voyageai-cli 1.27.0 → 1.28.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/package.json +1 -1
- package/src/commands/config.js +33 -0
- package/src/commands/mcp-server.js +4 -1
- package/src/commands/playground.js +19 -2
- package/src/commands/workflow.js +45 -0
- package/src/lib/api.js +40 -2
- package/src/lib/workflow.js +98 -2
- package/src/mcp/server.js +15 -2
- package/src/mcp/sse-transport.js +112 -0
- package/src/playground/index.html +1197 -232
|
@@ -992,8 +992,9 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
992
992
|
}
|
|
993
993
|
|
|
994
994
|
.explore-card-icon {
|
|
995
|
-
font-size: 28px;
|
|
996
995
|
margin-bottom: 8px;
|
|
996
|
+
color: var(--accent, #00D4AA);
|
|
997
|
+
opacity: 0.85;
|
|
997
998
|
}
|
|
998
999
|
.explore-card-title {
|
|
999
1000
|
font-size: 16px;
|
|
@@ -1056,7 +1057,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
1056
1057
|
padding: 24px 28px 16px;
|
|
1057
1058
|
border-bottom: 1px solid var(--border);
|
|
1058
1059
|
}
|
|
1059
|
-
.explore-modal-icon {
|
|
1060
|
+
.explore-modal-icon { color: var(--accent, #00D4AA); }
|
|
1060
1061
|
.explore-modal-title {
|
|
1061
1062
|
font-size: 18px;
|
|
1062
1063
|
font-weight: 600;
|
|
@@ -3030,6 +3031,24 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3030
3031
|
font-size: 10px; color: var(--text-muted); margin-top: 4px;
|
|
3031
3032
|
display: flex; gap: 8px;
|
|
3032
3033
|
}
|
|
3034
|
+
/* Collapsible examples section */
|
|
3035
|
+
.wf-library-section { padding: 0; margin-top: 4px; }
|
|
3036
|
+
.wf-library-section-toggle {
|
|
3037
|
+
display: flex; align-items: center; gap: 6px;
|
|
3038
|
+
width: 100%; padding: 8px 12px; border: none; background: none;
|
|
3039
|
+
color: var(--text-muted); font-size: 11px; font-weight: 600;
|
|
3040
|
+
text-transform: uppercase; letter-spacing: 0.5px; cursor: pointer;
|
|
3041
|
+
transition: color 0.15s;
|
|
3042
|
+
}
|
|
3043
|
+
.wf-library-section-toggle:hover { color: var(--text); }
|
|
3044
|
+
.wf-library-section-toggle .arrow {
|
|
3045
|
+
font-size: 8px; transition: transform 0.2s; display: inline-block;
|
|
3046
|
+
}
|
|
3047
|
+
.wf-library-section-toggle.open .arrow { transform: rotate(90deg); }
|
|
3048
|
+
.wf-library-category {
|
|
3049
|
+
font-size: 10px; font-weight: 600; color: var(--text-muted);
|
|
3050
|
+
padding: 8px 12px 2px; text-transform: uppercase; letter-spacing: 0.5px;
|
|
3051
|
+
}
|
|
3033
3052
|
.wf-canvas-area {
|
|
3034
3053
|
flex: 1; position: relative; overflow: hidden;
|
|
3035
3054
|
background: var(--bg);
|
|
@@ -3060,9 +3079,70 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3060
3079
|
}
|
|
3061
3080
|
.wf-canvas-toolbar .wf-run-btn:hover { opacity: 0.9; }
|
|
3062
3081
|
.wf-canvas-toolbar .wf-run-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
3082
|
+
.wf-canvas-toolbar .wf-new-btn {
|
|
3083
|
+
width: auto; padding: 0 12px; gap: 4px;
|
|
3084
|
+
font-weight: 600; font-size: 12px;
|
|
3085
|
+
background: var(--bg-card); border: 1px solid var(--accent);
|
|
3086
|
+
color: var(--accent);
|
|
3087
|
+
}
|
|
3088
|
+
.wf-canvas-toolbar .wf-new-btn:hover { background: var(--accent); color: #fff; }
|
|
3089
|
+
.wf-canvas-toolbar .wf-edit-btn {
|
|
3090
|
+
width: auto; padding: 0 12px; gap: 4px;
|
|
3091
|
+
font-weight: 600; font-size: 12px;
|
|
3092
|
+
background: var(--bg-card); border: 1px solid var(--border);
|
|
3093
|
+
color: var(--text-muted);
|
|
3094
|
+
}
|
|
3095
|
+
.wf-canvas-toolbar .wf-edit-btn:hover { border-color: var(--accent); color: var(--accent); }
|
|
3096
|
+
.wf-canvas-toolbar .wf-edit-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
3097
|
+
.wf-canvas-toolbar .wf-edit-btn:disabled:hover { border-color: var(--border); color: var(--text-muted); }
|
|
3063
3098
|
.wf-toolbar-sep {
|
|
3064
3099
|
width: 1px; height: 20px; background: var(--border); margin: 0 2px;
|
|
3065
3100
|
}
|
|
3101
|
+
/* Library / Palette tabs */
|
|
3102
|
+
.wf-library-tabs {
|
|
3103
|
+
display: flex; gap: 2px; width: 100%;
|
|
3104
|
+
background: var(--bg-input); border-radius: 6px; padding: 2px;
|
|
3105
|
+
}
|
|
3106
|
+
.wf-lib-tab {
|
|
3107
|
+
flex: 1; padding: 5px 0; border: none; border-radius: 4px;
|
|
3108
|
+
background: transparent; color: var(--text-muted); cursor: pointer;
|
|
3109
|
+
font-size: 11px; font-weight: 600; font-family: var(--font);
|
|
3110
|
+
transition: background 0.15s, color 0.15s; text-align: center;
|
|
3111
|
+
}
|
|
3112
|
+
.wf-lib-tab.active { background: var(--accent); color: #fff; }
|
|
3113
|
+
.wf-lib-tab:hover:not(.active) { color: var(--text); }
|
|
3114
|
+
/* Palette items */
|
|
3115
|
+
.wf-palette-category { margin-bottom: 8px; }
|
|
3116
|
+
.wf-palette-category-title {
|
|
3117
|
+
font-size: 10px; font-weight: 700; text-transform: uppercase;
|
|
3118
|
+
letter-spacing: 0.5px; color: var(--text-muted);
|
|
3119
|
+
padding: 4px 12px; margin-bottom: 2px;
|
|
3120
|
+
}
|
|
3121
|
+
.wf-palette-item {
|
|
3122
|
+
display: flex; align-items: center; gap: 8px;
|
|
3123
|
+
padding: 7px 12px; border-radius: 6px; cursor: grab;
|
|
3124
|
+
font-size: 12px; color: var(--text); transition: background 0.15s;
|
|
3125
|
+
}
|
|
3126
|
+
.wf-palette-item:hover { background: var(--bg-card); }
|
|
3127
|
+
.wf-palette-item:active { cursor: grabbing; }
|
|
3128
|
+
.wf-palette-icon { width: 20px; text-align: center; display: flex; align-items: center; justify-content: center; }
|
|
3129
|
+
.wf-palette-label { font-weight: 500; }
|
|
3130
|
+
/* Builder port styles */
|
|
3131
|
+
.wf-port-builder { cursor: crosshair; transition: r 0.15s; pointer-events: all !important; }
|
|
3132
|
+
.wf-port-builder:hover { r: 9; filter: brightness(1.4); }
|
|
3133
|
+
.wf-inspector-delete-btn {
|
|
3134
|
+
width: 100%; padding: 8px 0; border: none; border-radius: 6px;
|
|
3135
|
+
background: var(--error); color: #fff; cursor: pointer;
|
|
3136
|
+
font-size: 12px; font-weight: 600; margin-top: 12px;
|
|
3137
|
+
transition: opacity 0.15s;
|
|
3138
|
+
}
|
|
3139
|
+
.wf-inspector-delete-btn:hover { opacity: 0.85; }
|
|
3140
|
+
.wf-validation-errors {
|
|
3141
|
+
background: rgba(255,105,96,0.08); border: 1px solid var(--error);
|
|
3142
|
+
border-radius: 6px; padding: 10px 12px; margin-bottom: 12px;
|
|
3143
|
+
font-size: 11px; color: var(--error);
|
|
3144
|
+
}
|
|
3145
|
+
.wf-validation-errors ul { margin: 4px 0 0 16px; }
|
|
3066
3146
|
/* Dry-run plan overlay */
|
|
3067
3147
|
.wf-dryrun-overlay {
|
|
3068
3148
|
position: absolute; top: 0; left: 0; right: 0; bottom: 0;
|
|
@@ -3107,7 +3187,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3107
3187
|
background: var(--bg-card); border: 1px solid var(--border);
|
|
3108
3188
|
display: flex; align-items: center; gap: 10px; font-size: 12px;
|
|
3109
3189
|
}
|
|
3110
|
-
.wf-dryrun-step-icon {
|
|
3190
|
+
.wf-dryrun-step-icon { flex-shrink: 0; display: flex; align-items: center; }
|
|
3111
3191
|
.wf-dryrun-step-info { flex: 1; min-width: 0; }
|
|
3112
3192
|
.wf-dryrun-step-name { font-weight: 600; color: var(--text); }
|
|
3113
3193
|
.wf-dryrun-step-tool { color: var(--text-muted); font-size: 11px; }
|
|
@@ -3195,8 +3275,61 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3195
3275
|
transition: color 0.15s, border-color 0.15s;
|
|
3196
3276
|
}
|
|
3197
3277
|
.wf-output-expand-btn:hover { color: var(--text); border-color: var(--text-muted); }
|
|
3278
|
+
/* Input modal (pre-execution) */
|
|
3279
|
+
.wf-input-modal-backdrop {
|
|
3280
|
+
position: fixed; inset: 0; z-index: 9999;
|
|
3281
|
+
background: rgba(0,0,0,0.55); display: flex;
|
|
3282
|
+
align-items: center; justify-content: center;
|
|
3283
|
+
}
|
|
3284
|
+
.wf-input-modal {
|
|
3285
|
+
background: var(--bg-surface, var(--bg)); border: 1px solid var(--border);
|
|
3286
|
+
border-radius: 10px; width: 440px; max-width: 90vw;
|
|
3287
|
+
max-height: 80vh; display: flex; flex-direction: column;
|
|
3288
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
|
|
3289
|
+
}
|
|
3290
|
+
.wf-input-modal-header {
|
|
3291
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
3292
|
+
padding: 14px 20px; border-bottom: 1px solid var(--border);
|
|
3293
|
+
}
|
|
3294
|
+
.wf-input-modal-title { font-weight: 700; font-size: 14px; color: var(--text); }
|
|
3295
|
+
.wf-input-modal-body {
|
|
3296
|
+
flex: 1; overflow-y: auto; padding: 16px 20px;
|
|
3297
|
+
}
|
|
3298
|
+
.wf-input-modal-field { margin-bottom: 14px; }
|
|
3299
|
+
.wf-input-modal-label {
|
|
3300
|
+
font-size: 12px; font-weight: 600; color: var(--text); margin-bottom: 4px;
|
|
3301
|
+
}
|
|
3302
|
+
.wf-input-modal-desc {
|
|
3303
|
+
font-size: 11px; color: var(--text-muted); margin-bottom: 4px; line-height: 1.3;
|
|
3304
|
+
}
|
|
3305
|
+
.wf-input-modal-input {
|
|
3306
|
+
width: 100%; box-sizing: border-box; padding: 6px 10px;
|
|
3307
|
+
border: 1px solid var(--border); border-radius: 6px;
|
|
3308
|
+
background: var(--bg); color: var(--text); font-size: 13px;
|
|
3309
|
+
font-family: inherit; outline: none;
|
|
3310
|
+
transition: border-color 0.15s;
|
|
3311
|
+
}
|
|
3312
|
+
.wf-input-modal-input:focus { border-color: var(--accent, #6c63ff); }
|
|
3313
|
+
.wf-input-modal-input.error { border-color: #e74c3c; }
|
|
3314
|
+
.wf-input-modal-error { font-size: 11px; color: #e74c3c; margin-top: 3px; display: none; }
|
|
3315
|
+
.wf-input-modal-footer {
|
|
3316
|
+
display: flex; align-items: center; justify-content: flex-end; gap: 8px;
|
|
3317
|
+
padding: 12px 20px; border-top: 1px solid var(--border);
|
|
3318
|
+
}
|
|
3319
|
+
.wf-input-modal-cancel {
|
|
3320
|
+
padding: 6px 16px; border: 1px solid var(--border); border-radius: 6px;
|
|
3321
|
+
background: transparent; color: var(--text); font-size: 12px; cursor: pointer;
|
|
3322
|
+
transition: background 0.15s, border-color 0.15s;
|
|
3323
|
+
}
|
|
3324
|
+
.wf-input-modal-cancel:hover { background: var(--bg); border-color: var(--text-muted); }
|
|
3325
|
+
.wf-input-modal-run {
|
|
3326
|
+
padding: 6px 16px; border: none; border-radius: 6px;
|
|
3327
|
+
background: var(--accent, #6c63ff); color: #fff; font-size: 12px;
|
|
3328
|
+
font-weight: 600; cursor: pointer; transition: filter 0.15s;
|
|
3329
|
+
}
|
|
3330
|
+
.wf-input-modal-run:hover { filter: brightness(1.15); }
|
|
3198
3331
|
#wf-canvas {
|
|
3199
|
-
width: 100%; height: 100%; display: block;
|
|
3332
|
+
width: 100%; height: 100%; display: block; position: relative; z-index: 1;
|
|
3200
3333
|
}
|
|
3201
3334
|
/* SVG node styles */
|
|
3202
3335
|
.wf-node { cursor: pointer; }
|
|
@@ -3211,8 +3344,8 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3211
3344
|
pointer-events: none; text-anchor: middle; dominant-baseline: central;
|
|
3212
3345
|
}
|
|
3213
3346
|
.wf-node-icon {
|
|
3214
|
-
|
|
3215
|
-
|
|
3347
|
+
pointer-events: none;
|
|
3348
|
+
color: #fff;
|
|
3216
3349
|
}
|
|
3217
3350
|
.wf-node-badge {
|
|
3218
3351
|
font-size: 10px; fill: rgba(255,255,255,0.7);
|
|
@@ -3374,7 +3507,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3374
3507
|
}
|
|
3375
3508
|
/* Workflow visualizer light mode */
|
|
3376
3509
|
[data-theme="light"] .wf-node-label { fill: #001E2B; }
|
|
3377
|
-
[data-theme="light"] .wf-node-icon {
|
|
3510
|
+
[data-theme="light"] .wf-node-icon { color: #001E2B; }
|
|
3378
3511
|
[data-theme="light"] .wf-node-badge { fill: rgba(0,30,43,0.55); }
|
|
3379
3512
|
[data-theme="light"] .wf-node-time { fill: rgba(0,30,43,0.5); }
|
|
3380
3513
|
[data-theme="light"] .wf-node-condition { fill: #944F01; }
|
|
@@ -3396,6 +3529,8 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3396
3529
|
[data-theme="light"] .wf-exec-status { box-shadow: 0 2px 8px rgba(0,30,43,0.08); }
|
|
3397
3530
|
[data-theme="light"] .wf-run-btn { background: var(--accent); }
|
|
3398
3531
|
[data-theme="light"] .wf-output-modal { box-shadow: 0 8px 32px rgba(0,30,43,0.15); }
|
|
3532
|
+
[data-theme="light"] .wf-input-modal { box-shadow: 0 8px 32px rgba(0,30,43,0.15); }
|
|
3533
|
+
[data-theme="light"] .wf-input-modal-backdrop { background: rgba(0,0,0,0.3); }
|
|
3399
3534
|
@keyframes wf-pulse-light {
|
|
3400
3535
|
0%, 100% { filter: drop-shadow(0 0 4px rgba(0,158,128,0.3)); }
|
|
3401
3536
|
50% { filter: drop-shadow(0 0 12px rgba(0,158,128,0.6)); }
|
|
@@ -3406,61 +3541,78 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3406
3541
|
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
|
3407
3542
|
text-align: center; color: var(--text-muted); pointer-events: none;
|
|
3408
3543
|
}
|
|
3409
|
-
.wf-canvas-empty-icon {
|
|
3544
|
+
.wf-canvas-empty-icon { margin-bottom: 16px; opacity: 0.15; }
|
|
3545
|
+
.wf-canvas-empty-icon img { width: 200px; height: 200px; filter: invert(1); }
|
|
3546
|
+
[data-theme="light"] .wf-canvas-empty-icon img { filter: none; opacity: 0.1; }
|
|
3410
3547
|
.wf-canvas-empty-text { font-size: 14px; }
|
|
3548
|
+
/* Persistent watermark behind workflows */
|
|
3549
|
+
.wf-canvas-watermark {
|
|
3550
|
+
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
|
3551
|
+
pointer-events: none; z-index: 0; opacity: 0.04;
|
|
3552
|
+
}
|
|
3553
|
+
.wf-canvas-watermark img { width: 300px; height: 300px; filter: invert(1); }
|
|
3554
|
+
[data-theme="light"] .wf-canvas-watermark img { filter: none; }
|
|
3411
3555
|
</style>
|
|
3412
3556
|
</head>
|
|
3413
3557
|
<body>
|
|
3414
3558
|
|
|
3415
|
-
<!--
|
|
3559
|
+
<!-- Lucide Icons (lucide.dev) — stroke-based, 16×16 -->
|
|
3416
3560
|
<svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
|
|
3417
|
-
|
|
3418
|
-
|
|
3561
|
+
<!-- Zap (Embed) -->
|
|
3562
|
+
<symbol id="lg-lightning" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3563
|
+
<path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"/>
|
|
3419
3564
|
</symbol>
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
<path d="
|
|
3423
|
-
<path d="M11 7.42721V2.57279C11 2.04351 11.6076 1.79846 11.9279 2.19856L13.871 4.62577C14.043 4.84058 14.043 5.15942 13.871 5.37423L11.9279 7.80144C11.6076 8.20154 11 7.95649 11 7.42721Z" fill="currentColor"/>
|
|
3424
|
-
<path d="M3 4.5C3 4.22386 3.22386 4 3.5 4H11V6H3.5C3.22386 6 3 5.77614 3 5.5V4.5Z" fill="currentColor"/>
|
|
3565
|
+
<!-- Arrow Left Right (Compare) -->
|
|
3566
|
+
<symbol id="lg-arrows" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3567
|
+
<path d="M8 3 4 7l4 4"/><path d="M4 7h16"/><path d="m16 21 4-4-4-4"/><path d="M20 17H4"/>
|
|
3425
3568
|
</symbol>
|
|
3426
|
-
|
|
3427
|
-
|
|
3569
|
+
<!-- Search -->
|
|
3570
|
+
<symbol id="lg-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3571
|
+
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>
|
|
3428
3572
|
</symbol>
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
<path d="
|
|
3573
|
+
<!-- Gauge (Benchmark) -->
|
|
3574
|
+
<symbol id="lg-gauge" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3575
|
+
<path d="m12 14 4-4"/><path d="M3.34 19a10 10 0 1 1 17.32 0"/>
|
|
3432
3576
|
</symbol>
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
<path d="
|
|
3436
|
-
<path d="M10 14V13H6V14C6 14.5523 6.44772 15 7 15H9C9.55228 15 10 14.5523 10 14Z" fill="currentColor"/>
|
|
3577
|
+
<!-- Lightbulb (Explore) -->
|
|
3578
|
+
<symbol id="lg-bulb" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3579
|
+
<path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"/><path d="M9 18h6"/><path d="M10 22h4"/>
|
|
3437
3580
|
</symbol>
|
|
3438
|
-
|
|
3439
|
-
|
|
3581
|
+
<!-- Info (About) -->
|
|
3582
|
+
<symbol id="lg-info" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3583
|
+
<circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/>
|
|
3440
3584
|
</symbol>
|
|
3441
|
-
|
|
3442
|
-
|
|
3585
|
+
<!-- Image (Multimodal) -->
|
|
3586
|
+
<symbol id="lg-image" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3587
|
+
<rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/>
|
|
3443
3588
|
</symbol>
|
|
3444
|
-
|
|
3445
|
-
|
|
3589
|
+
<!-- Settings (Config) -->
|
|
3590
|
+
<symbol id="lg-config" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3591
|
+
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/>
|
|
3446
3592
|
</symbol>
|
|
3447
|
-
|
|
3448
|
-
|
|
3593
|
+
<!-- Code (Generate) -->
|
|
3594
|
+
<symbol id="lg-code" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3595
|
+
<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>
|
|
3449
3596
|
</symbol>
|
|
3450
|
-
|
|
3451
|
-
|
|
3597
|
+
<!-- Palette (Theme) -->
|
|
3598
|
+
<symbol id="lg-palette" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3599
|
+
<circle cx="13.5" cy="6.5" r=".5" fill="currentColor"/><circle cx="17.5" cy="10.5" r=".5" fill="currentColor"/><circle cx="8.5" cy="7.5" r=".5" fill="currentColor"/><circle cx="6.5" cy="12.5" r=".5" fill="currentColor"/><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z"/>
|
|
3452
3600
|
</symbol>
|
|
3453
|
-
|
|
3454
|
-
|
|
3601
|
+
<!-- Box (Cube) -->
|
|
3602
|
+
<symbol id="lg-cube" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3603
|
+
<path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/>
|
|
3455
3604
|
</symbol>
|
|
3456
|
-
|
|
3457
|
-
|
|
3605
|
+
<!-- Message Square (Chat) -->
|
|
3606
|
+
<symbol id="lg-chat" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3607
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
|
3458
3608
|
</symbol>
|
|
3459
|
-
|
|
3460
|
-
|
|
3609
|
+
<!-- Shield -->
|
|
3610
|
+
<symbol id="lg-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3611
|
+
<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 .5-.87l7-4a1 1 0 0 1 1 0l7 4A1 1 0 0 1 20 6z"/>
|
|
3461
3612
|
</symbol>
|
|
3462
|
-
|
|
3463
|
-
|
|
3613
|
+
<!-- Activity (Pulse) -->
|
|
3614
|
+
<symbol id="lg-pulse" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3615
|
+
<path d="M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36-3.18-19.64A2 2 0 0 0 10.12 1h-.24a2 2 0 0 0-1.94 1.55L5.18 12H2"/>
|
|
3464
3616
|
</symbol>
|
|
3465
3617
|
</svg>
|
|
3466
3618
|
|
|
@@ -3471,7 +3623,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3471
3623
|
<div class="sidebar-drag-region">
|
|
3472
3624
|
<img class="sidebar-logo" id="sidebarLogo" src="/icons/dark/64.png" alt="Vai">
|
|
3473
3625
|
<span class="sidebar-title">Vai</span>
|
|
3474
|
-
<button class="sidebar-settings-btn" data-tab="settings" title="Settings"><svg width="16" height="16" viewBox="0 0
|
|
3626
|
+
<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>
|
|
3475
3627
|
</div>
|
|
3476
3628
|
<nav class="sidebar-nav">
|
|
3477
3629
|
<div class="sidebar-nav-group" role="tablist" aria-label="Tools">
|
|
@@ -3481,8 +3633,8 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3481
3633
|
<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>
|
|
3482
3634
|
<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>
|
|
3483
3635
|
<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>
|
|
3484
|
-
<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
|
|
3485
|
-
<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
|
|
3636
|
+
<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>
|
|
3637
|
+
<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>
|
|
3486
3638
|
</div>
|
|
3487
3639
|
<div class="sidebar-nav-divider"></div>
|
|
3488
3640
|
<div class="sidebar-nav-group" role="tablist" aria-label="Learn">
|
|
@@ -3581,7 +3733,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3581
3733
|
<option value="ubinary">ubinary (32× smaller)</option>
|
|
3582
3734
|
</select>
|
|
3583
3735
|
</div>
|
|
3584
|
-
<button class="btn" id="embedBtn" onclick="doEmbed()"
|
|
3736
|
+
<button class="btn" id="embedBtn" onclick="doEmbed()"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px;"><path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"/></svg>Embed</button>
|
|
3585
3737
|
</div>
|
|
3586
3738
|
|
|
3587
3739
|
<div class="error-msg" id="embedError"></div>
|
|
@@ -3592,7 +3744,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3592
3744
|
<div id="embedStats"></div>
|
|
3593
3745
|
<div class="vector-preview" id="embedVector"></div>
|
|
3594
3746
|
<div style="margin-top:8px;">
|
|
3595
|
-
<button class="btn btn-secondary btn-small" onclick="copyVector()"
|
|
3747
|
+
<button class="btn btn-secondary btn-small" onclick="copyVector()"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-1px;margin-right:3px;"><path d="M9 9V6a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-3M3 15a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3z"/></svg>Copy Full Vector</button>
|
|
3596
3748
|
</div>
|
|
3597
3749
|
<div class="card-title" style="margin-top:16px;">Vector Heatmap</div>
|
|
3598
3750
|
<div class="heatmap" id="embedHeatmap"></div>
|
|
@@ -3634,7 +3786,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3634
3786
|
<option value="2048">2048</option>
|
|
3635
3787
|
</select>
|
|
3636
3788
|
</div>
|
|
3637
|
-
<button class="btn" id="compareBtn" onclick="doCompare()"
|
|
3789
|
+
<button class="btn" id="compareBtn" onclick="doCompare()"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px;"><path d="M8 3 4 7l4 4M4 7h16M16 21l4-4-4-4M20 17H4"/></svg>Compare</button>
|
|
3638
3790
|
</div>
|
|
3639
3791
|
|
|
3640
3792
|
<div class="error-msg" id="compareError"></div>
|
|
@@ -3696,8 +3848,8 @@ Semantic search understands meaning beyond keyword matching</textarea>
|
|
|
3696
3848
|
<option value="10">10</option>
|
|
3697
3849
|
</select>
|
|
3698
3850
|
</div>
|
|
3699
|
-
<button class="btn" id="searchBtn" onclick="doSearch(false)"
|
|
3700
|
-
<button class="btn btn-secondary" id="searchRerankBtn" onclick="doSearch(true)"
|
|
3851
|
+
<button class="btn" id="searchBtn" onclick="doSearch(false)"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px;"><path d="M21 21l-4.3-4.3M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z"/></svg>Search</button>
|
|
3852
|
+
<button class="btn btn-secondary" id="searchRerankBtn" onclick="doSearch(true)"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px;"><path d="M3 6h18M7 12h10M10 18h4"/></svg>Rerank</button>
|
|
3701
3853
|
</div>
|
|
3702
3854
|
|
|
3703
3855
|
<div class="error-msg" id="searchError"></div>
|
|
@@ -3756,7 +3908,7 @@ Semantic search understands meaning beyond keyword matching</textarea>
|
|
|
3756
3908
|
<option value="2048">2048</option>
|
|
3757
3909
|
</select>
|
|
3758
3910
|
</div>
|
|
3759
|
-
<button class="btn" id="mmCompareBtn" onclick="doMultimodalCompare()"
|
|
3911
|
+
<button class="btn" id="mmCompareBtn" onclick="doMultimodalCompare()"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px;"><path d="M8 3 4 7l4 4M4 7h16M16 21l4-4-4-4M20 17H4"/></svg>Compare</button>
|
|
3760
3912
|
</div>
|
|
3761
3913
|
|
|
3762
3914
|
<div class="error-msg" id="mmError"></div>
|
|
@@ -3794,20 +3946,20 @@ Semantic search understands meaning beyond keyword matching</textarea>
|
|
|
3794
3946
|
<div class="card" style="margin-top:12px;">
|
|
3795
3947
|
<div class="card-title">Search Query</div>
|
|
3796
3948
|
<div class="mm-search-mode" id="mmSearchMode">
|
|
3797
|
-
<button class="active" data-mode="text" onclick="setMmSearchMode('text')"
|
|
3798
|
-
<button data-mode="image" onclick="setMmSearchMode('image')"
|
|
3949
|
+
<button class="active" data-mode="text" onclick="setMmSearchMode('text')"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:3px;"><path d="M17 6.1H3M21 12.1H3M15.1 18H3"/></svg>Text Query</button>
|
|
3950
|
+
<button data-mode="image" onclick="setMmSearchMode('image')"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:3px;"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>Image Query</button>
|
|
3799
3951
|
</div>
|
|
3800
3952
|
<div id="mmSearchTextWrap">
|
|
3801
3953
|
<div class="mm-search-row">
|
|
3802
3954
|
<input type="text" id="mmSearchQuery" placeholder="Enter a search query...">
|
|
3803
|
-
<button class="btn" id="mmSearchBtn" onclick="doMultimodalSearch()"
|
|
3955
|
+
<button class="btn" id="mmSearchBtn" onclick="doMultimodalSearch()"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px;"><path d="M21 21l-4.3-4.3M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z"/></svg>Search Corpus</button>
|
|
3804
3956
|
</div>
|
|
3805
3957
|
</div>
|
|
3806
3958
|
<div id="mmSearchImageWrap" style="display:none;">
|
|
3807
3959
|
<p style="font-size:13px;color:var(--text-dim);margin-bottom:8px;">Click an image in the corpus above to use it as the search query, then:</p>
|
|
3808
3960
|
<div style="display:flex;gap:8px;align-items:center;">
|
|
3809
3961
|
<span id="mmSearchImageLabel" style="font-size:13px;color:var(--text-muted);">No image selected</span>
|
|
3810
|
-
<button class="btn" id="mmSearchImgBtn" onclick="doMultimodalSearch()"
|
|
3962
|
+
<button class="btn" id="mmSearchImgBtn" onclick="doMultimodalSearch()"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px;"><path d="M21 21l-4.3-4.3M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z"/></svg>Search Corpus</button>
|
|
3811
3963
|
</div>
|
|
3812
3964
|
</div>
|
|
3813
3965
|
</div>
|
|
@@ -3834,12 +3986,12 @@ Semantic search understands meaning beyond keyword matching</textarea>
|
|
|
3834
3986
|
|
|
3835
3987
|
<!-- Sub-panel switcher -->
|
|
3836
3988
|
<div class="bench-panels">
|
|
3837
|
-
<button class="bench-panel-btn active" data-bench="latency"
|
|
3838
|
-
<button class="bench-panel-btn" data-bench="ranking"
|
|
3839
|
-
<button class="bench-panel-btn" data-bench="competitors"
|
|
3840
|
-
<button class="bench-panel-btn" data-bench="quantization"
|
|
3841
|
-
<button class="bench-panel-btn" data-bench="cost"
|
|
3842
|
-
<button class="bench-panel-btn" data-bench="history"
|
|
3989
|
+
<button class="bench-panel-btn active" data-bench="latency"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:3px;"><path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"/></svg>Latency</button>
|
|
3990
|
+
<button class="bench-panel-btn" data-bench="ranking"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:3px;"><path d="M12 20V10M18 20V4M6 20v-4"/></svg>Ranking</button>
|
|
3991
|
+
<button class="bench-panel-btn" data-bench="competitors"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:3px;"><path d="M16 3h5v5M8 3H3v5M12 22v-8.3a4 4 0 0 0-1.172-2.872L3 3M21 3l-7.828 7.828A4 4 0 0 0 12 13.7V22"/></svg>vs Competitors</button>
|
|
3992
|
+
<button class="bench-panel-btn" data-bench="quantization"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:3px;"><path d="M10 2v7.527a2 2 0 0 1-.211.896L4.72 20.55a1 1 0 0 0 .9 1.45h12.76a1 1 0 0 0 .9-1.45l-5.069-10.127A2 2 0 0 1 14 9.527V2M8.5 2h7M7 16h10"/></svg>Quantization</button>
|
|
3993
|
+
<button class="bench-panel-btn" data-bench="cost"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:3px;"><path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>Cost</button>
|
|
3994
|
+
<button class="bench-panel-btn" data-bench="history"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:3px;"><path d="M12 20V10M18 20V4M6 20v-4"/></svg>History</button>
|
|
3843
3995
|
</div>
|
|
3844
3996
|
|
|
3845
3997
|
<!-- ── Latency Panel ── -->
|
|
@@ -3861,7 +4013,7 @@ Semantic search understands meaning beyond keyword matching</textarea>
|
|
|
3861
4013
|
<option value="10">10</option>
|
|
3862
4014
|
</select>
|
|
3863
4015
|
</div>
|
|
3864
|
-
<button class="btn" id="benchLatencyBtn" onclick="doBenchLatency()"
|
|
4016
|
+
<button class="btn" id="benchLatencyBtn" onclick="doBenchLatency()"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px;"><path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"/></svg>Run Benchmark</button>
|
|
3865
4017
|
</div>
|
|
3866
4018
|
</div>
|
|
3867
4019
|
|
|
@@ -3915,7 +4067,7 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
3915
4067
|
<option value="8">8</option>
|
|
3916
4068
|
</select>
|
|
3917
4069
|
</div>
|
|
3918
|
-
<button class="btn" id="benchRankBtn" onclick="doBenchRanking()"
|
|
4070
|
+
<button class="btn" id="benchRankBtn" onclick="doBenchRanking()"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px;"><path d="M12 20V10M18 20V4M6 20v-4"/></svg>Compare Rankings</button>
|
|
3919
4071
|
</div>
|
|
3920
4072
|
</div>
|
|
3921
4073
|
|
|
@@ -3991,7 +4143,7 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
3991
4143
|
|
|
3992
4144
|
<!-- Cost Comparison -->
|
|
3993
4145
|
<div class="card" style="margin-top:16px;">
|
|
3994
|
-
<div class="card-title"
|
|
4146
|
+
<div class="card-title"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:3px;"><path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>Cost per Million Tokens</div>
|
|
3995
4147
|
<p style="color:var(--text-dim);font-size:13px;margin-bottom:16px;">
|
|
3996
4148
|
Voyage AI offers significant cost savings, especially with asymmetric retrieval strategies.
|
|
3997
4149
|
</p>
|
|
@@ -4152,7 +4304,7 @@ Approximate nearest neighbor algorithms like HNSW enable fast similarity search
|
|
|
4152
4304
|
Reranking models rescore initial search results to improve relevance ordering.</textarea>
|
|
4153
4305
|
</div>
|
|
4154
4306
|
<div style="margin-top:12px;">
|
|
4155
|
-
<button class="btn" id="quantBtn" onclick="doBenchQuantization()"
|
|
4307
|
+
<button class="btn" id="quantBtn" onclick="doBenchQuantization()"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px;"><path d="M10 2v7.527a2 2 0 0 1-.211.896L4.72 20.55a1 1 0 0 0 .9 1.45h12.76a1 1 0 0 0 .9-1.45l-5.069-10.127A2 2 0 0 1 14 9.527V2M8.5 2h7M7 16h10"/></svg>Run Quantization Benchmark</button>
|
|
4156
4308
|
</div>
|
|
4157
4309
|
</div>
|
|
4158
4310
|
|
|
@@ -4180,7 +4332,7 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4180
4332
|
<!-- ── Cost Panel ── -->
|
|
4181
4333
|
<div class="bench-view" id="bench-cost">
|
|
4182
4334
|
<div class="card">
|
|
4183
|
-
<div class="card-title"
|
|
4335
|
+
<div class="card-title"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px;"><path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>RAG Cost Calculator <button class="cost-help-btn" id="costHelpBtn" title="How the math works">?</button></div>
|
|
4184
4336
|
|
|
4185
4337
|
<!-- Mode toggle -->
|
|
4186
4338
|
<div style="margin-bottom: 20px;">
|
|
@@ -4301,7 +4453,7 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4301
4453
|
<div class="history-empty">No benchmarks recorded yet. Run a latency benchmark to start tracking.</div>
|
|
4302
4454
|
</div>
|
|
4303
4455
|
<div style="margin-top:12px;text-align:right;">
|
|
4304
|
-
<button class="btn btn-secondary btn-small" onclick="clearHistory()"
|
|
4456
|
+
<button class="btn btn-secondary btn-small" onclick="clearHistory()"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-1px;margin-right:3px;"><path d="M3 6h18M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/></svg>Clear History</button>
|
|
4305
4457
|
</div>
|
|
4306
4458
|
</div>
|
|
4307
4459
|
</div>
|
|
@@ -4318,7 +4470,7 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4318
4470
|
<span id="chatStatusDb">No database</span>
|
|
4319
4471
|
</div>
|
|
4320
4472
|
<button class="chat-config-toggle" id="chatOpenSettings" onclick="openChatSettings()" title="Configure in Settings">
|
|
4321
|
-
<svg width="14" height="14" viewBox="0 0
|
|
4473
|
+
<svg width="14" height="14" viewBox="0 0 24 24"><use href="#lg-config"/></svg>
|
|
4322
4474
|
Configure
|
|
4323
4475
|
</button>
|
|
4324
4476
|
</div>
|
|
@@ -4342,11 +4494,15 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4342
4494
|
<div class="wf-container">
|
|
4343
4495
|
<div class="wf-library">
|
|
4344
4496
|
<div class="wf-library-header">
|
|
4345
|
-
<
|
|
4497
|
+
<div class="wf-library-tabs">
|
|
4498
|
+
<button class="wf-lib-tab active" data-lib-tab="library" onclick="wfSwitchLibTab('library')">Library</button>
|
|
4499
|
+
<button class="wf-lib-tab" data-lib-tab="palette" onclick="wfSwitchLibTab('palette')">Palette</button>
|
|
4500
|
+
</div>
|
|
4346
4501
|
</div>
|
|
4347
4502
|
<div class="wf-library-list" id="wfLibraryList">
|
|
4348
4503
|
<div style="padding: 16px; color: var(--text-muted); font-size: 12px;">Loading...</div>
|
|
4349
4504
|
</div>
|
|
4505
|
+
<div class="wf-palette-list" id="wfPaletteList" style="display:none; flex:1; overflow-y:auto; padding:8px;"></div>
|
|
4350
4506
|
<div class="wf-library-footer">
|
|
4351
4507
|
<button class="wf-load-file-btn" onclick="wfLoadFromFile()" title="Load workflow JSON from file">
|
|
4352
4508
|
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 14h10M8 2v9M5 8l3 3 3-3"/></svg>
|
|
@@ -4357,6 +4513,9 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4357
4513
|
</div>
|
|
4358
4514
|
<div class="wf-canvas-area">
|
|
4359
4515
|
<div class="wf-canvas-toolbar" id="wfToolbar">
|
|
4516
|
+
<button class="wf-new-btn" onclick="wfNewWorkflow()" title="Create new workflow">+ New</button>
|
|
4517
|
+
<button class="wf-edit-btn" id="wfEditBtn" onclick="wfEditWorkflow()" disabled title="Edit current workflow">✎ Edit</button>
|
|
4518
|
+
<span class="wf-toolbar-sep"></span>
|
|
4360
4519
|
<button onclick="wfZoom(1)" title="Zoom in">+</button>
|
|
4361
4520
|
<button onclick="wfZoom(-1)" title="Zoom out">−</button>
|
|
4362
4521
|
<button onclick="wfFitToView()" title="Fit to view">⊞</button>
|
|
@@ -4377,10 +4536,11 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4377
4536
|
<span class="wf-exec-status-time" id="wfExecStatusTime"></span>
|
|
4378
4537
|
</div>
|
|
4379
4538
|
<div class="wf-canvas-empty" id="wfCanvasEmpty">
|
|
4380
|
-
<div class="wf-canvas-empty-icon"
|
|
4539
|
+
<div class="wf-canvas-empty-icon"><img src="/icons/watermark.png" alt="Vai"></div>
|
|
4381
4540
|
<div class="wf-canvas-empty-text">Select a workflow from the library</div>
|
|
4382
4541
|
</div>
|
|
4383
|
-
<
|
|
4542
|
+
<div class="wf-canvas-watermark" id="wfCanvasWatermark"><img src="/icons/watermark.png" alt=""></div>
|
|
4543
|
+
<svg id="wf-canvas" xmlns="http://www.w3.org/2000/svg" ondragover="event.preventDefault()" ondrop="wfCanvasDrop(event)"></svg>
|
|
4384
4544
|
</div>
|
|
4385
4545
|
<div class="wf-inspector collapsed" id="wfInspector">
|
|
4386
4546
|
<button class="wf-inspector-toggle" id="wfInspectorToggle" onclick="wfToggleInspector()" title="Toggle inspector">‹</button>
|
|
@@ -4411,6 +4571,21 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4411
4571
|
</div>
|
|
4412
4572
|
</div>
|
|
4413
4573
|
|
|
4574
|
+
<!-- ── Workflow Input Modal (pre-execution) ── -->
|
|
4575
|
+
<div class="wf-input-modal-backdrop" id="wfInputModalBackdrop" style="display:none;" onclick="wfCloseInputModal()">
|
|
4576
|
+
<div class="wf-input-modal" onclick="event.stopPropagation()">
|
|
4577
|
+
<div class="wf-input-modal-header">
|
|
4578
|
+
<span class="wf-input-modal-title" id="wfInputModalTitle">Workflow Inputs</span>
|
|
4579
|
+
<button class="wf-output-modal-btn close" onclick="wfCloseInputModal()" title="Close">×</button>
|
|
4580
|
+
</div>
|
|
4581
|
+
<div class="wf-input-modal-body" id="wfInputModalBody"></div>
|
|
4582
|
+
<div class="wf-input-modal-footer">
|
|
4583
|
+
<button class="wf-input-modal-cancel" onclick="wfCloseInputModal()">Cancel</button>
|
|
4584
|
+
<button class="wf-input-modal-run" onclick="wfInputModalSubmit()">Run Workflow</button>
|
|
4585
|
+
</div>
|
|
4586
|
+
</div>
|
|
4587
|
+
</div>
|
|
4588
|
+
|
|
4414
4589
|
<!-- ========== ABOUT TAB ========== -->
|
|
4415
4590
|
<div class="tab-panel" id="tab-about" role="tabpanel" aria-labelledby="tab-btn-about" tabindex="0">
|
|
4416
4591
|
<div class="about-container">
|
|
@@ -4421,9 +4596,9 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4421
4596
|
<div class="about-name">Michael Lynn</div>
|
|
4422
4597
|
<div class="about-role">Principal Staff Developer Advocate · MongoDB</div>
|
|
4423
4598
|
<div class="about-links">
|
|
4424
|
-
<a href="https://github.com/mrlynn" target="_blank" rel="noopener"
|
|
4425
|
-
<a href="https://mlynn.org" target="_blank" rel="noopener"
|
|
4426
|
-
<a href="https://www.npmjs.com/package/voyageai-cli" target="_blank" rel="noopener"
|
|
4599
|
+
<a href="https://github.com/mrlynn" target="_blank" rel="noopener"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-1px;margin-right:3px;"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>GitHub</a>
|
|
4600
|
+
<a href="https://mlynn.org" target="_blank" rel="noopener"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-1px;margin-right:3px;"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>mlynn.org</a>
|
|
4601
|
+
<a href="https://www.npmjs.com/package/voyageai-cli" target="_blank" rel="noopener"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-1px;margin-right:3px;"><path d="M16.5 9.4l-9-5.19M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16zM3.27 6.96 12 12.01l8.73-5.05M12 22.08V12"/></svg>npm</a>
|
|
4427
4602
|
</div>
|
|
4428
4603
|
</div>
|
|
4429
4604
|
</div>
|
|
@@ -4519,11 +4694,11 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4519
4694
|
<!-- Mode Toggle -->
|
|
4520
4695
|
<div class="subtabs" role="tablist">
|
|
4521
4696
|
<button class="subtab active" id="genModeCode" onclick="setGenerateMode('code')" role="tab" aria-selected="true">
|
|
4522
|
-
<svg viewBox="0 0
|
|
4697
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
|
|
4523
4698
|
Generate Code
|
|
4524
4699
|
</button>
|
|
4525
4700
|
<button class="subtab" id="genModeScaffold" onclick="setGenerateMode('scaffold')" role="tab" aria-selected="false">
|
|
4526
|
-
<svg viewBox="0 0
|
|
4701
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 13h4"/><path d="M10 17h4"/><path d="M10 9h1"/></svg>
|
|
4527
4702
|
Scaffold Project
|
|
4528
4703
|
</button>
|
|
4529
4704
|
</div>
|
|
@@ -4665,7 +4840,7 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4665
4840
|
</div>
|
|
4666
4841
|
<div id="scaffoldWebButtons" style="display:none;">
|
|
4667
4842
|
<button class="btn btn-primary" onclick="downloadScaffoldZip()" id="scaffoldDownloadBtn">
|
|
4668
|
-
<svg width="16" height="16" viewBox="0 0
|
|
4843
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right:6px;"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>
|
|
4669
4844
|
Download ZIP
|
|
4670
4845
|
</button>
|
|
4671
4846
|
</div>
|
|
@@ -4722,31 +4897,31 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4722
4897
|
<nav class="settings-nav">
|
|
4723
4898
|
<div class="settings-nav-header">Settings</div>
|
|
4724
4899
|
<button class="settings-nav-item active" data-settings-section="general">
|
|
4725
|
-
<svg width="16" height="16" viewBox="0 0
|
|
4900
|
+
<svg width="16" height="16" viewBox="0 0 24 24"><use href="#lg-config"/></svg>
|
|
4726
4901
|
<span>General</span>
|
|
4727
4902
|
</button>
|
|
4728
4903
|
<button class="settings-nav-item" data-settings-section="appearance">
|
|
4729
|
-
<svg width="16" height="16" viewBox="0 0
|
|
4904
|
+
<svg width="16" height="16" viewBox="0 0 24 24"><use href="#lg-palette"/></svg>
|
|
4730
4905
|
<span>Appearance</span>
|
|
4731
4906
|
</button>
|
|
4732
4907
|
<button class="settings-nav-item" data-settings-section="models">
|
|
4733
|
-
<svg width="16" height="16" viewBox="0 0
|
|
4908
|
+
<svg width="16" height="16" viewBox="0 0 24 24"><use href="#lg-cube"/></svg>
|
|
4734
4909
|
<span>Models</span>
|
|
4735
4910
|
</button>
|
|
4736
4911
|
<button class="settings-nav-item" data-settings-section="chat">
|
|
4737
|
-
<svg width="16" height="16" viewBox="0 0
|
|
4912
|
+
<svg width="16" height="16" viewBox="0 0 24 24"><use href="#lg-chat"/></svg>
|
|
4738
4913
|
<span>Chat</span>
|
|
4739
4914
|
</button>
|
|
4740
4915
|
<button class="settings-nav-item" data-settings-section="benchmark">
|
|
4741
|
-
<svg width="16" height="16" viewBox="0 0
|
|
4916
|
+
<svg width="16" height="16" viewBox="0 0 24 24"><use href="#lg-gauge"/></svg>
|
|
4742
4917
|
<span>Benchmark</span>
|
|
4743
4918
|
</button>
|
|
4744
4919
|
<button class="settings-nav-item" data-settings-section="privacy">
|
|
4745
|
-
<svg width="16" height="16" viewBox="0 0
|
|
4920
|
+
<svg width="16" height="16" viewBox="0 0 24 24"><use href="#lg-shield"/></svg>
|
|
4746
4921
|
<span>Data & Privacy</span>
|
|
4747
4922
|
</button>
|
|
4748
4923
|
<button class="settings-nav-item" data-settings-section="health">
|
|
4749
|
-
<svg width="16" height="16" viewBox="0 0
|
|
4924
|
+
<svg width="16" height="16" viewBox="0 0 24 24"><use href="#lg-pulse"/></svg>
|
|
4750
4925
|
<span>Health Check</span>
|
|
4751
4926
|
</button>
|
|
4752
4927
|
</nav>
|
|
@@ -4768,12 +4943,12 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4768
4943
|
<div class="settings-row">
|
|
4769
4944
|
<div class="settings-label">
|
|
4770
4945
|
<span class="settings-label-text">API Key <span class="settings-origin" data-origin-key="apiKey"></span></span>
|
|
4771
|
-
<span class="settings-label-hint">Encrypted via OS keychain · <a href="https://dash.voyageai.com" target="_blank" class="settings-key-link"
|
|
4946
|
+
<span class="settings-label-hint">Encrypted via OS keychain · <a href="https://dash.voyageai.com" target="_blank" class="settings-key-link"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-1px;margin-right:2px;"><path d="M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z"/><circle cx="16.5" cy="7.5" r=".5" fill="currentColor"/></svg>Get a key</a></span>
|
|
4772
4947
|
</div>
|
|
4773
4948
|
<div class="settings-control" style="min-width:260px;">
|
|
4774
4949
|
<div class="settings-api-field">
|
|
4775
4950
|
<input type="password" id="settingsApiKey" placeholder="pa-..." autocomplete="off" spellcheck="false">
|
|
4776
|
-
<button type="button" id="settingsApiKeyToggle" title="Show/hide key"
|
|
4951
|
+
<button type="button" id="settingsApiKeyToggle" title="Show/hide key"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"/><circle cx="12" cy="12" r="3"/></svg></button>
|
|
4777
4952
|
<button type="button" id="settingsApiKeySave" class="save-btn" title="Save key">Save</button>
|
|
4778
4953
|
</div>
|
|
4779
4954
|
</div>
|
|
@@ -4927,7 +5102,7 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4927
5102
|
title="Show/hide API key"
|
|
4928
5103
|
style="padding:8px 12px;min-width:auto"
|
|
4929
5104
|
>
|
|
4930
|
-
|
|
5105
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"/><circle cx="12" cy="12" r="3"/></svg>
|
|
4931
5106
|
</button>
|
|
4932
5107
|
<button
|
|
4933
5108
|
class="btn"
|
|
@@ -4936,7 +5111,7 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4936
5111
|
title="Save API key"
|
|
4937
5112
|
style="padding:8px 12px;min-width:auto"
|
|
4938
5113
|
>
|
|
4939
|
-
|
|
5114
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z"/><path d="M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7"/><path d="M7 3v4a1 1 0 0 0 1 1h7"/></svg>
|
|
4940
5115
|
</button>
|
|
4941
5116
|
</div>
|
|
4942
5117
|
</div>
|
|
@@ -5952,12 +6127,12 @@ async function downloadScaffoldZip() {
|
|
|
5952
6127
|
// Show success
|
|
5953
6128
|
btn.innerHTML = '<span style="margin-right:6px;">✓</span> Downloaded!';
|
|
5954
6129
|
setTimeout(() => {
|
|
5955
|
-
btn.innerHTML = '<svg width="16" height="16" viewBox="0 0
|
|
6130
|
+
btn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right:6px;"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg> Download ZIP';
|
|
5956
6131
|
btn.disabled = false;
|
|
5957
6132
|
}, 2000);
|
|
5958
6133
|
} catch (err) {
|
|
5959
6134
|
alert('Error: ' + err.message);
|
|
5960
|
-
btn.innerHTML = '<svg width="16" height="16" viewBox="0 0
|
|
6135
|
+
btn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right:6px;"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg> Download ZIP';
|
|
5961
6136
|
btn.disabled = false;
|
|
5962
6137
|
}
|
|
5963
6138
|
}
|
|
@@ -6053,37 +6228,73 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
6053
6228
|
});
|
|
6054
6229
|
|
|
6055
6230
|
// ── Explore: icons and tab mappings per concept ──
|
|
6231
|
+
// Lucide SVG icon helper — returns an inline <svg> string
|
|
6232
|
+
function lucideIcon(d, size = 16) {
|
|
6233
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="${d}"/></svg>`;
|
|
6234
|
+
}
|
|
6235
|
+
|
|
6236
|
+
// Lucide path constants for concept/button icons
|
|
6237
|
+
const LI = {
|
|
6238
|
+
zap: 'M13 2 3 14h9l-1 8 10-12h-9l1-8z',
|
|
6239
|
+
search: 'M21 21l-4.3-4.3M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z',
|
|
6240
|
+
trophy: 'M6 9H4.5a2.5 2.5 0 0 1 0-5H6M18 9h1.5a2.5 2.5 0 0 0 0-5H18M4 22h16M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22M18 2H6v7a6 6 0 0 0 12 0V2z',
|
|
6241
|
+
bot: 'M12 8V4H8M8 2h8M2 14a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2zM6 14v4M10 14v4M14 14v4M18 14v4',
|
|
6242
|
+
ruler: 'M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z M15 5l4 4',
|
|
6243
|
+
target: 'M12 12m-10 0a10 10 0 1 0 20 0 10 10 0 1 0-20 0M12 12m-6 0a6 6 0 1 0 12 0 6 6 0 1 0-12 0M12 12m-2 0a2 2 0 1 0 4 0 2 2 0 1 0-4 0',
|
|
6244
|
+
tag: 'M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42zM7.5 7.5m-.5 0a.5.5 0 1 0 1 0 .5.5 0 1 0-1 0',
|
|
6245
|
+
brain: 'M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96.44 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 1.98-3A2.5 2.5 0 0 1 9.5 2zM14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-1.98-3A2.5 2.5 0 0 0 14.5 2z',
|
|
6246
|
+
key: 'M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4zM16.5 7.5m-.5 0a.5.5 0 1 0 1 0 .5.5 0 1 0-1 0',
|
|
6247
|
+
globe: 'M12 12m-10 0a10 10 0 1 0 20 0 10 10 0 1 0-20 0M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z',
|
|
6248
|
+
package: 'M16.5 9.4l-9-5.19M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16zM3.27 6.96 12 12.01l8.73-5.05M12 22.08V12',
|
|
6249
|
+
timer: 'M10 2h4M12 14l3-3M12 22a8 8 0 1 0 0-16 8 8 0 0 0 0 16z',
|
|
6250
|
+
flask: 'M10 2v7.527a2 2 0 0 1-.211.896L4.72 20.55a1 1 0 0 0 .9 1.45h12.76a1 1 0 0 0 .9-1.45l-5.069-10.127A2 2 0 0 1 14 9.527V2M8.5 2h7M7 16h10',
|
|
6251
|
+
puzzle: 'M19.439 7.85c-.049.322.059.648.289.878l1.568 1.568c.47.47.706 1.087.706 1.704s-.235 1.233-.706 1.704l-1.611 1.611a.98.98 0 0 1-.837.276c-.47-.07-.802-.48-.968-.925a2.501 2.501 0 1 0-3.214 3.214c.446.166.855.497.925.968a.979.979 0 0 1-.276.837l-1.61 1.61a2.404 2.404 0 0 1-1.705.707 2.402 2.402 0 0 1-1.704-.706l-1.568-1.568a1.026 1.026 0 0 0-.877-.29c-.493.074-.84.504-1.02.968a2.5 2.5 0 1 1-3.237-3.237c.464-.18.894-.527.967-1.02a1.026 1.026 0 0 0-.289-.877l-1.568-1.568A2.402 2.402 0 0 1 1.998 12c0-.617.236-1.234.706-1.704L4.23 8.77c.24-.24.581-.353.917-.303.515.077.877.528 1.073 1.01a2.5 2.5 0 1 0 3.259-3.259c-.482-.196-.933-.558-1.01-1.073-.05-.336.062-.676.303-.917l1.525-1.525A2.402 2.402 0 0 1 12 2c.617 0 1.234.236 1.704.706l1.568 1.568c.23.23.556.338.878.29.493-.074.84-.504 1.02-.968a2.5 2.5 0 1 1 3.237 3.237c-.464.18-.894.527-.968 1.02z',
|
|
6252
|
+
link: 'M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71',
|
|
6253
|
+
barChart: 'M12 20V10M18 20V4M6 20v-4',
|
|
6254
|
+
microscope: 'M6 18h8M3 22h18M14 22a7 7 0 1 0 0-14h-1M9 14h2M9 12a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2z',
|
|
6255
|
+
image: 'M3 3h18a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2zM8.5 10a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zM21 15l-5-5L5 21',
|
|
6256
|
+
shuffle: 'M2 18h1.4c1.3 0 2.5-.6 3.3-1.7l6.1-8.6c.7-1.1 2-1.7 3.3-1.7H22M18 2l4 4-4 4M2 6h1.9c1.5 0 2.9.9 3.6 2.2M22 18l-4 4-4-4M19 14h3',
|
|
6257
|
+
circle: 'M12 12m-10 0a10 10 0 1 0 20 0 10 10 0 1 0-20 0',
|
|
6258
|
+
fileTxt: 'M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7zM14 2v4a2 2 0 0 0 2 2h4M10 13h4M10 17h4M10 9h1',
|
|
6259
|
+
scale: 'M16 3h5v5M8 3H3v5M12 22v-8.3a4 4 0 0 0-1.172-2.872L3 3M21 3l-7.828 7.828A4 4 0 0 0 12 13.7V22',
|
|
6260
|
+
laptop: 'M20 16V7a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v9M2 20h20M12 12h.01',
|
|
6261
|
+
blocks: 'M2 12h10v10H2zM14 4l6 3.5v7L14 18l-6-3.5v-7zM12 2l10 6',
|
|
6262
|
+
refresh: 'M21 2v6h-6M3 12a9 9 0 0 1 15-6.7L21 8M3 22v-6h6M21 12a9 9 0 0 1-15 6.7L3 16',
|
|
6263
|
+
sparkle: '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',
|
|
6264
|
+
download: 'M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3',
|
|
6265
|
+
copy: 'M9 9V6a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-3M3 15a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3z',
|
|
6266
|
+
filter: 'M3 6h18M7 12h10M10 18h4',
|
|
6267
|
+
};
|
|
6268
|
+
|
|
6056
6269
|
const CONCEPT_META = {
|
|
6057
|
-
embeddings: { icon:
|
|
6058
|
-
reranking: { icon:
|
|
6059
|
-
'vector-search': { icon:
|
|
6060
|
-
rag: { icon:
|
|
6061
|
-
'cosine-similarity': { icon:
|
|
6062
|
-
'two-stage-retrieval': { icon:
|
|
6063
|
-
'input-type': { icon:
|
|
6064
|
-
models: { icon:
|
|
6065
|
-
'api-keys': { icon:
|
|
6066
|
-
'api-access': { icon:
|
|
6067
|
-
'batch-processing': { icon:
|
|
6068
|
-
benchmarking: { icon:
|
|
6069
|
-
quantization: { icon:
|
|
6070
|
-
'mixture-of-experts': { icon:
|
|
6071
|
-
'shared-embedding-space': { icon:
|
|
6072
|
-
'rteb-benchmarks': { icon:
|
|
6073
|
-
'voyage-4-nano': { icon:
|
|
6074
|
-
'rerank-eval': { icon:
|
|
6075
|
-
'multimodal-embeddings': { icon:
|
|
6076
|
-
'cross-modal-search': { icon:
|
|
6077
|
-
'modality-gap': { icon:
|
|
6078
|
-
'multimodal-rag': { icon:
|
|
6079
|
-
'provider-comparison': { icon:
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
'
|
|
6084
|
-
|
|
6085
|
-
'auto-embedding': { icon: '⚡', tab: 'explore' },
|
|
6086
|
-
'vai-vs-auto-embedding': { icon: '🔄', tab: 'explore' },
|
|
6270
|
+
embeddings: { icon: LI.zap, tab: 'embed' },
|
|
6271
|
+
reranking: { icon: LI.trophy, tab: 'search' },
|
|
6272
|
+
'vector-search': { icon: LI.search, tab: 'search' },
|
|
6273
|
+
rag: { icon: LI.bot, tab: 'search' },
|
|
6274
|
+
'cosine-similarity': { icon: LI.ruler, tab: 'compare' },
|
|
6275
|
+
'two-stage-retrieval': { icon: LI.target, tab: 'search' },
|
|
6276
|
+
'input-type': { icon: LI.tag, tab: 'embed' },
|
|
6277
|
+
models: { icon: LI.brain, tab: 'embed' },
|
|
6278
|
+
'api-keys': { icon: LI.key, tab: 'embed' },
|
|
6279
|
+
'api-access': { icon: LI.globe, tab: 'embed' },
|
|
6280
|
+
'batch-processing': { icon: LI.package, tab: 'embed' },
|
|
6281
|
+
benchmarking: { icon: LI.timer, tab: 'benchmark' },
|
|
6282
|
+
quantization: { icon: LI.flask, tab: 'benchmark' },
|
|
6283
|
+
'mixture-of-experts': { icon: LI.puzzle, tab: 'embed' },
|
|
6284
|
+
'shared-embedding-space': { icon: LI.link, tab: 'compare' },
|
|
6285
|
+
'rteb-benchmarks': { icon: LI.barChart, tab: 'benchmark' },
|
|
6286
|
+
'voyage-4-nano': { icon: LI.microscope, tab: 'embed' },
|
|
6287
|
+
'rerank-eval': { icon: LI.ruler, tab: 'benchmark' },
|
|
6288
|
+
'multimodal-embeddings': { icon: LI.image, tab: 'multimodal' },
|
|
6289
|
+
'cross-modal-search': { icon: LI.shuffle, tab: 'multimodal' },
|
|
6290
|
+
'modality-gap': { icon: LI.circle, tab: 'multimodal' },
|
|
6291
|
+
'multimodal-rag': { icon: LI.fileTxt, tab: 'multimodal' },
|
|
6292
|
+
'provider-comparison': { icon: LI.scale, tab: 'explore' },
|
|
6293
|
+
'code-generation': { icon: LI.laptop, tab: 'explore' },
|
|
6294
|
+
scaffolding: { icon: LI.blocks, tab: 'explore' },
|
|
6295
|
+
'eval-comparison': { icon: LI.barChart, tab: 'benchmark' },
|
|
6296
|
+
'auto-embedding': { icon: LI.zap, tab: 'explore' },
|
|
6297
|
+
'vai-vs-auto-embedding': { icon: LI.refresh, tab: 'explore' },
|
|
6087
6298
|
};
|
|
6088
6299
|
|
|
6089
6300
|
let exploreConcepts = {};
|
|
@@ -6109,13 +6320,13 @@ function buildExploreCards() {
|
|
|
6109
6320
|
grid.innerHTML = '';
|
|
6110
6321
|
|
|
6111
6322
|
for (const [key, concept] of Object.entries(exploreConcepts)) {
|
|
6112
|
-
const meta = CONCEPT_META[key] || { icon:
|
|
6323
|
+
const meta = CONCEPT_META[key] || { icon: LI.package, tab: 'embed' };
|
|
6113
6324
|
const card = document.createElement('div');
|
|
6114
6325
|
card.className = 'explore-card';
|
|
6115
6326
|
card.dataset.key = key;
|
|
6116
6327
|
|
|
6117
6328
|
card.innerHTML = `
|
|
6118
|
-
<div class="explore-card-icon">${meta.icon}</div>
|
|
6329
|
+
<div class="explore-card-icon">${lucideIcon(meta.icon, 28)}</div>
|
|
6119
6330
|
<div class="explore-card-title">${escapeHtml(concept.title)}</div>
|
|
6120
6331
|
<div class="explore-card-summary">${escapeHtml(concept.summary)}</div>
|
|
6121
6332
|
`;
|
|
@@ -6142,12 +6353,12 @@ let exploreModalPreviousFocus = null;
|
|
|
6142
6353
|
function openExploreModal(key) {
|
|
6143
6354
|
const concept = exploreConcepts[key];
|
|
6144
6355
|
if (!concept) return;
|
|
6145
|
-
const meta = CONCEPT_META[key] || { icon:
|
|
6356
|
+
const meta = CONCEPT_META[key] || { icon: LI.package, tab: 'embed' };
|
|
6146
6357
|
|
|
6147
6358
|
// Save the currently focused element to restore when modal closes
|
|
6148
6359
|
exploreModalPreviousFocus = document.activeElement;
|
|
6149
6360
|
|
|
6150
|
-
document.getElementById('exploreModalIcon').
|
|
6361
|
+
document.getElementById('exploreModalIcon').innerHTML = lucideIcon(meta.icon, 32);
|
|
6151
6362
|
document.getElementById('exploreModalTitle').textContent = concept.title;
|
|
6152
6363
|
document.getElementById('exploreModalSummary').textContent = concept.summary;
|
|
6153
6364
|
|
|
@@ -9790,19 +10001,26 @@ const WF_NODE_META = {
|
|
|
9790
10001
|
search: { icon: '\u{1F50E}', label: 'Vector Search', color: '#40E0FF', category: 'retrieval' },
|
|
9791
10002
|
rerank: { icon: '\u{1F3C6}', label: 'Rerank', color: '#40E0FF', category: 'retrieval' },
|
|
9792
10003
|
ingest: { icon: '\u{1F4E5}', label: 'Ingest', color: '#40E0FF', category: 'retrieval' },
|
|
9793
|
-
embed: { icon: '
|
|
9794
|
-
similarity: { icon: '
|
|
9795
|
-
collections: { icon: '
|
|
9796
|
-
models: { icon: '
|
|
9797
|
-
estimate: { icon: '
|
|
9798
|
-
explain: { icon: '
|
|
9799
|
-
topics: { icon: '
|
|
9800
|
-
merge: { icon: '
|
|
9801
|
-
filter: { icon: '
|
|
9802
|
-
transform: { icon: '
|
|
9803
|
-
generate: { icon: '
|
|
10004
|
+
embed: { icon: 'M13 2 3 14h9l-1 8 10-12h-9l1-8z', label: 'Embed', color: '#B388FF', category: 'embedding' },
|
|
10005
|
+
similarity: { icon: 'M8 3 4 7l4 4M4 7h16M16 21l4-4-4-4M20 17H4', label: 'Similarity', color: '#B388FF', category: 'embedding' },
|
|
10006
|
+
collections: { icon: 'M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z', label: 'Collections', color: '#00D4AA', category: 'management' },
|
|
10007
|
+
models: { icon: 'M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2M9 5h6M9 14l2 2 4-4', label: 'Models', color: '#00D4AA', category: 'management' },
|
|
10008
|
+
estimate: { icon: 'M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6', label: 'Cost Estimate', color: '#FFB74D', category: 'utility' },
|
|
10009
|
+
explain: { icon: 'M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2zM22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z', label: 'Explain', color: '#FFB74D', category: 'utility' },
|
|
10010
|
+
topics: { icon: 'M12 6V2M8 18H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-4M12 18v4M8 22h8', label: 'Topics', color: '#FFB74D', category: 'utility' },
|
|
10011
|
+
merge: { icon: 'M18 21a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM6 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM6 21a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM6 9v6M18 15l-6-6-6 6', label: 'Merge', color: '#90A4AE', category: 'control' },
|
|
10012
|
+
filter: { icon: 'M22 3H2l8 9.46V19l4 2v-8.54L22 3z', label: 'Filter', color: '#90A4AE', category: 'control' },
|
|
10013
|
+
transform: { icon: 'M21 2v6h-6M3 12a9 9 0 0 1 15-6.7L21 8M3 22v-6h6M21 12a9 9 0 0 1-15 6.7L3 16', label: 'Transform', color: '#90A4AE', category: 'control' },
|
|
10014
|
+
generate: { icon: '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', label: 'Generate', color: '#69F0AE', category: 'generation' },
|
|
10015
|
+
query: { icon: 'M21 21l-4.3-4.3M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z', label: 'Query', color: '#64B5F6', category: 'retrieval' },
|
|
10016
|
+
rerank: { icon: 'M3 6h18M7 12h10M10 18h4', label: 'Rerank', color: '#CE93D8', category: 'retrieval' },
|
|
10017
|
+
search: { icon: 'M21 21l-4.3-4.3M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z', label: 'Search', color: '#64B5F6', category: 'retrieval' },
|
|
10018
|
+
ingest: { icon: 'M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3', label: 'Ingest', color: '#4DB6AC', category: 'management' },
|
|
9804
10019
|
};
|
|
9805
10020
|
|
|
10021
|
+
// Fallback icon (gear) for unknown workflow node types
|
|
10022
|
+
const WF_FALLBACK_ICON = 'M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2zM12 12m-3 0a3 3 0 1 0 6 0 3 3 0 1 0-6 0';
|
|
10023
|
+
|
|
9806
10024
|
const WF_NODE_W = 180;
|
|
9807
10025
|
const WF_NODE_H = 64;
|
|
9808
10026
|
const WF_LAYER_GAP = 260;
|
|
@@ -9812,6 +10030,7 @@ const WF_PORT_R = 5; // Port circle radius
|
|
|
9812
10030
|
|
|
9813
10031
|
let wfState = {
|
|
9814
10032
|
workflows: [],
|
|
10033
|
+
examples: [],
|
|
9815
10034
|
activeWorkflow: null,
|
|
9816
10035
|
selectedNodeId: null,
|
|
9817
10036
|
executionState: {},
|
|
@@ -9825,14 +10044,24 @@ let wfState = {
|
|
|
9825
10044
|
isPanning: false,
|
|
9826
10045
|
panStart: { x: 0, y: 0 },
|
|
9827
10046
|
executing: false,
|
|
10047
|
+
// Builder state
|
|
10048
|
+
builderMode: false,
|
|
10049
|
+
draggingEdge: null,
|
|
10050
|
+
dragNode: null,
|
|
10051
|
+
dirtyFlag: false,
|
|
9828
10052
|
};
|
|
9829
10053
|
|
|
9830
10054
|
// ── Library ──
|
|
9831
10055
|
async function wfLoadLibrary() {
|
|
9832
10056
|
try {
|
|
9833
|
-
const
|
|
9834
|
-
|
|
9835
|
-
|
|
10057
|
+
const [wfRes, exRes] = await Promise.all([
|
|
10058
|
+
fetch('/api/workflows'),
|
|
10059
|
+
fetch('/api/workflows/examples'),
|
|
10060
|
+
]);
|
|
10061
|
+
const wfData = await wfRes.json();
|
|
10062
|
+
const exData = await exRes.json();
|
|
10063
|
+
wfState.workflows = wfData.workflows || [];
|
|
10064
|
+
wfState.examples = exData.examples || [];
|
|
9836
10065
|
wfRenderLibrary();
|
|
9837
10066
|
} catch (err) {
|
|
9838
10067
|
const list = document.getElementById('wfLibraryList');
|
|
@@ -9843,19 +10072,53 @@ async function wfLoadLibrary() {
|
|
|
9843
10072
|
function wfRenderLibrary() {
|
|
9844
10073
|
const list = document.getElementById('wfLibraryList');
|
|
9845
10074
|
if (!list) return;
|
|
9846
|
-
if (wfState.workflows.length === 0) {
|
|
10075
|
+
if (wfState.workflows.length === 0 && (!wfState.examples || wfState.examples.length === 0)) {
|
|
9847
10076
|
list.innerHTML = '<div style="padding:16px;color:var(--text-muted);font-size:12px;">No workflows found</div>';
|
|
9848
10077
|
return;
|
|
9849
10078
|
}
|
|
9850
|
-
|
|
9851
|
-
|
|
9852
|
-
|
|
10079
|
+
|
|
10080
|
+
// Built-in templates
|
|
10081
|
+
let html = wfState.workflows.map(w => {
|
|
9853
10082
|
const displayName = w.name.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
9854
10083
|
return `<div class="wf-library-item" data-wf-name="${w.name}" onclick="wfSelectWorkflow('${w.name}')">
|
|
9855
10084
|
<div class="wf-library-item-name">${displayName}</div>
|
|
9856
10085
|
<div class="wf-library-item-desc">${w.description || ''}</div>
|
|
9857
10086
|
</div>`;
|
|
9858
10087
|
}).join('');
|
|
10088
|
+
|
|
10089
|
+
// Collapsible examples section
|
|
10090
|
+
const examples = wfState.examples || [];
|
|
10091
|
+
if (examples.length > 0) {
|
|
10092
|
+
html += `<div class="wf-library-section">
|
|
10093
|
+
<button class="wf-library-section-toggle" onclick="wfToggleExamples(this)">
|
|
10094
|
+
<span class="arrow">▶</span> Examples (${examples.length})
|
|
10095
|
+
</button>
|
|
10096
|
+
<div class="wf-examples-content" style="display:none;">`;
|
|
10097
|
+
|
|
10098
|
+
const categories = ['Retrieval', 'RAG', 'Ingestion', 'Analysis', 'Other'];
|
|
10099
|
+
for (const cat of categories) {
|
|
10100
|
+
const items = examples.filter(e => e.category === cat);
|
|
10101
|
+
if (items.length === 0) continue;
|
|
10102
|
+
html += `<div class="wf-library-category">${cat}</div>`;
|
|
10103
|
+
html += items.map(w => {
|
|
10104
|
+
const displayName = w.name.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
10105
|
+
return `<div class="wf-library-item" data-wf-name="${w.name}" onclick="wfSelectWorkflow('${w.name}')">
|
|
10106
|
+
<div class="wf-library-item-name">${displayName}</div>
|
|
10107
|
+
<div class="wf-library-item-desc">${w.description || ''}</div>
|
|
10108
|
+
</div>`;
|
|
10109
|
+
}).join('');
|
|
10110
|
+
}
|
|
10111
|
+
html += '</div></div>';
|
|
10112
|
+
}
|
|
10113
|
+
|
|
10114
|
+
list.innerHTML = html;
|
|
10115
|
+
}
|
|
10116
|
+
|
|
10117
|
+
function wfToggleExamples(btn) {
|
|
10118
|
+
const content = btn.nextElementSibling;
|
|
10119
|
+
const isOpen = content.style.display !== 'none';
|
|
10120
|
+
content.style.display = isOpen ? 'none' : '';
|
|
10121
|
+
btn.classList.toggle('open', !isOpen);
|
|
9859
10122
|
}
|
|
9860
10123
|
|
|
9861
10124
|
async function wfSelectWorkflow(name) {
|
|
@@ -9868,12 +10131,14 @@ async function wfSelectWorkflow(name) {
|
|
|
9868
10131
|
const res = await fetch('/api/workflows/' + encodeURIComponent(name));
|
|
9869
10132
|
const data = await res.json();
|
|
9870
10133
|
wfState.activeWorkflow = data.definition;
|
|
10134
|
+
wfState.builderMode = false;
|
|
9871
10135
|
wfState.selectedNodeId = null;
|
|
9872
10136
|
wfState.executionState = {};
|
|
9873
10137
|
wfState.executionResults = {};
|
|
9874
10138
|
wfSetToolbarEnabled(true);
|
|
9875
10139
|
document.getElementById('wfCanvasEmpty').style.display = 'none';
|
|
9876
10140
|
await wfRenderWorkflow(data.definition);
|
|
10141
|
+
wfSwitchLibTab('library');
|
|
9877
10142
|
wfOpenInspector();
|
|
9878
10143
|
wfUpdateInspector();
|
|
9879
10144
|
} catch (err) {
|
|
@@ -10024,7 +10289,7 @@ async function wfRenderWorkflow(definition) {
|
|
|
10024
10289
|
}
|
|
10025
10290
|
|
|
10026
10291
|
function wfDrawNode(step, x, y, state, hasDeps, hasDependents) {
|
|
10027
|
-
const meta = WF_NODE_META[step.tool] || { icon:
|
|
10292
|
+
const meta = WF_NODE_META[step.tool] || { icon: WF_FALLBACK_ICON, label: step.tool, color: '#666', category: 'unknown' };
|
|
10028
10293
|
const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
10029
10294
|
g.classList.add('wf-node');
|
|
10030
10295
|
if (state !== 'idle') g.classList.add('wf-node--' + state);
|
|
@@ -10036,6 +10301,39 @@ function wfDrawNode(step, x, y, state, hasDeps, hasDependents) {
|
|
|
10036
10301
|
wfSelectNode(step.id);
|
|
10037
10302
|
});
|
|
10038
10303
|
|
|
10304
|
+
// Builder: node drag
|
|
10305
|
+
if (wfState.builderMode) {
|
|
10306
|
+
g.style.cursor = 'move';
|
|
10307
|
+
g.addEventListener('mousedown', (e) => {
|
|
10308
|
+
// Don't drag if clicking a port
|
|
10309
|
+
if (e.target.classList.contains('wf-port')) return;
|
|
10310
|
+
e.stopPropagation();
|
|
10311
|
+
const svg = document.getElementById('wf-canvas');
|
|
10312
|
+
const rect = svg.getBoundingClientRect();
|
|
10313
|
+
const startSvgX = (e.clientX - rect.left) / wfState.zoom + wfState.panX;
|
|
10314
|
+
const startSvgY = (e.clientY - rect.top) / wfState.zoom + wfState.panY;
|
|
10315
|
+
const pos = wfState.nodePositions[step.id];
|
|
10316
|
+
if (!pos) return;
|
|
10317
|
+
const offX = startSvgX - pos.x;
|
|
10318
|
+
const offY = startSvgY - pos.y;
|
|
10319
|
+
wfState.dragNode = step.id;
|
|
10320
|
+
|
|
10321
|
+
function onMove(ev) {
|
|
10322
|
+
const mx = (ev.clientX - rect.left) / wfState.zoom + wfState.panX;
|
|
10323
|
+
const my = (ev.clientY - rect.top) / wfState.zoom + wfState.panY;
|
|
10324
|
+
wfState.nodePositions[step.id] = { x: mx - offX, y: my - offY };
|
|
10325
|
+
wfRefreshNodes();
|
|
10326
|
+
}
|
|
10327
|
+
function onUp() {
|
|
10328
|
+
wfState.dragNode = null;
|
|
10329
|
+
document.removeEventListener('mousemove', onMove);
|
|
10330
|
+
document.removeEventListener('mouseup', onUp);
|
|
10331
|
+
}
|
|
10332
|
+
document.addEventListener('mousemove', onMove);
|
|
10333
|
+
document.addEventListener('mouseup', onUp);
|
|
10334
|
+
});
|
|
10335
|
+
}
|
|
10336
|
+
|
|
10039
10337
|
// Background rect
|
|
10040
10338
|
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
10041
10339
|
rect.setAttribute('width', WF_NODE_W);
|
|
@@ -10045,37 +10343,66 @@ function wfDrawNode(step, x, y, state, hasDeps, hasDependents) {
|
|
|
10045
10343
|
rect.setAttribute('opacity', '0.85');
|
|
10046
10344
|
g.appendChild(rect);
|
|
10047
10345
|
|
|
10048
|
-
// Input port (left side): only if
|
|
10049
|
-
|
|
10346
|
+
// Input port (left side): show in builder mode always, otherwise only if has deps
|
|
10347
|
+
const showInPort = wfState.builderMode || hasDeps;
|
|
10348
|
+
if (showInPort) {
|
|
10050
10349
|
const inPort = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
10051
10350
|
inPort.classList.add('wf-port', 'wf-port-in');
|
|
10351
|
+
if (wfState.builderMode) inPort.classList.add('wf-port-builder');
|
|
10052
10352
|
inPort.setAttribute('cx', 0);
|
|
10053
10353
|
inPort.setAttribute('cy', WF_NODE_H / 2);
|
|
10054
|
-
inPort.setAttribute('r', WF_PORT_R);
|
|
10354
|
+
inPort.setAttribute('r', wfState.builderMode ? 7 : WF_PORT_R);
|
|
10055
10355
|
inPort.setAttribute('stroke', meta.color);
|
|
10056
10356
|
inPort.setAttribute('stroke-width', '2');
|
|
10357
|
+
inPort.setAttribute('pointer-events', 'all');
|
|
10358
|
+
if (wfState.builderMode) {
|
|
10359
|
+
inPort.addEventListener('mouseup', () => {
|
|
10360
|
+
if (wfState.draggingEdge) wfEdgeDropOnInput(step.id);
|
|
10361
|
+
});
|
|
10362
|
+
}
|
|
10057
10363
|
g.appendChild(inPort);
|
|
10058
10364
|
}
|
|
10059
10365
|
|
|
10060
|
-
// Output port (right side):
|
|
10061
|
-
|
|
10366
|
+
// Output port (right side): show in builder mode always, otherwise only if has dependents
|
|
10367
|
+
const showOutPort = wfState.builderMode || hasDependents;
|
|
10368
|
+
if (showOutPort) {
|
|
10062
10369
|
const outPort = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
10063
10370
|
outPort.classList.add('wf-port', 'wf-port-out');
|
|
10371
|
+
if (wfState.builderMode) outPort.classList.add('wf-port-builder');
|
|
10064
10372
|
outPort.setAttribute('cx', WF_NODE_W);
|
|
10065
10373
|
outPort.setAttribute('cy', WF_NODE_H / 2);
|
|
10066
|
-
outPort.setAttribute('r', WF_PORT_R);
|
|
10374
|
+
outPort.setAttribute('r', wfState.builderMode ? 7 : WF_PORT_R);
|
|
10067
10375
|
outPort.setAttribute('fill', meta.color);
|
|
10068
10376
|
outPort.setAttribute('stroke-width', '2');
|
|
10377
|
+
outPort.setAttribute('pointer-events', 'all');
|
|
10378
|
+
if (wfState.builderMode) {
|
|
10379
|
+
outPort.addEventListener('mousedown', (e) => {
|
|
10380
|
+
e.stopPropagation();
|
|
10381
|
+
const pos = wfState.nodePositions[step.id];
|
|
10382
|
+
if (!pos) return;
|
|
10383
|
+
wfEdgeDragStart(step.id, pos.x + WF_NODE_W, pos.y + WF_NODE_H / 2);
|
|
10384
|
+
});
|
|
10385
|
+
}
|
|
10069
10386
|
g.appendChild(outPort);
|
|
10070
10387
|
}
|
|
10071
10388
|
|
|
10072
|
-
// Icon
|
|
10073
|
-
const
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10389
|
+
// Icon (Lucide SVG)
|
|
10390
|
+
const iconG = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
10391
|
+
iconG.classList.add('wf-node-icon');
|
|
10392
|
+
const iconSize = 18;
|
|
10393
|
+
const ix = 22 - iconSize / 2;
|
|
10394
|
+
const iy = WF_NODE_H / 2 - iconSize / 2;
|
|
10395
|
+
iconG.setAttribute('transform', `translate(${ix},${iy}) scale(${iconSize / 24})`);
|
|
10396
|
+
const fullD = meta.icon || WF_FALLBACK_ICON;
|
|
10397
|
+
const iconPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
10398
|
+
iconPath.setAttribute('d', fullD);
|
|
10399
|
+
iconPath.setAttribute('fill', 'none');
|
|
10400
|
+
iconPath.setAttribute('stroke', 'currentColor');
|
|
10401
|
+
iconPath.setAttribute('stroke-width', '1.75');
|
|
10402
|
+
iconPath.setAttribute('stroke-linecap', 'round');
|
|
10403
|
+
iconPath.setAttribute('stroke-linejoin', 'round');
|
|
10404
|
+
iconG.appendChild(iconPath);
|
|
10405
|
+
g.appendChild(iconG);
|
|
10079
10406
|
|
|
10080
10407
|
// Label (step name, truncated)
|
|
10081
10408
|
const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
@@ -10242,44 +10569,69 @@ function wfUpdateInspector() {
|
|
|
10242
10569
|
header.textContent = def.name || 'Workflow';
|
|
10243
10570
|
let html = '';
|
|
10244
10571
|
|
|
10245
|
-
|
|
10246
|
-
|
|
10572
|
+
if (wfState.builderMode) {
|
|
10573
|
+
// Builder: editable workflow fields
|
|
10574
|
+
html += `<div class="wf-inspector-section">
|
|
10575
|
+
<div class="wf-inspector-section-title">Name</div>
|
|
10576
|
+
<input class="wf-inspector-input" value="${escapeHtml(def.name || '')}" onchange="wfEditWorkflowField('name', this.value); document.getElementById('wfInspectorHeader').textContent = this.value || 'Workflow';">
|
|
10577
|
+
</div>`;
|
|
10247
10578
|
html += `<div class="wf-inspector-section">
|
|
10248
10579
|
<div class="wf-inspector-section-title">Description</div>
|
|
10249
|
-
<
|
|
10580
|
+
<textarea class="wf-inspector-input" rows="2" style="resize:vertical;" onchange="wfEditWorkflowField('description', this.value)">${escapeHtml(def.description || '')}</textarea>
|
|
10250
10581
|
</div>`;
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
10254
|
-
|
|
10255
|
-
|
|
10256
|
-
|
|
10257
|
-
|
|
10258
|
-
|
|
10259
|
-
|
|
10260
|
-
|
|
10261
|
-
|
|
10262
|
-
|
|
10582
|
+
html += `<div class="wf-inspector-section">
|
|
10583
|
+
<div class="wf-inspector-section-title">Steps</div>
|
|
10584
|
+
<div style="font-size:12px;color:var(--text);">${def.steps.length} step${def.steps.length !== 1 ? 's' : ''}</div>
|
|
10585
|
+
</div>`;
|
|
10586
|
+
if (def.steps.length > 0) {
|
|
10587
|
+
html += `<div class="wf-inspector-section">
|
|
10588
|
+
<div class="wf-inspector-section-title">Output Mapping</div>
|
|
10589
|
+
<textarea class="wf-inspector-input" rows="3" style="resize:vertical;font-family:monospace;font-size:11px;" onchange="try { wfEditWorkflowField('output', JSON.parse(this.value)); } catch(e) {}">${escapeHtml(JSON.stringify(def.output || {}, null, 2))}</textarea>
|
|
10590
|
+
</div>`;
|
|
10591
|
+
}
|
|
10592
|
+
html += `<div class="wf-inspector-section" style="margin-top:8px;">
|
|
10593
|
+
<div style="font-size:10px;color:var(--text-muted);">Add steps from the Palette tab, then drag between ports to connect them.</div>
|
|
10594
|
+
</div>`;
|
|
10595
|
+
} else {
|
|
10596
|
+
// Read-only: Description
|
|
10597
|
+
if (def.description) {
|
|
10598
|
+
html += `<div class="wf-inspector-section">
|
|
10599
|
+
<div class="wf-inspector-section-title">Description</div>
|
|
10600
|
+
<div style="font-size:12px;color:var(--text);line-height:1.4;">${escapeHtml(def.description)}</div>
|
|
10263
10601
|
</div>`;
|
|
10264
10602
|
}
|
|
10265
|
-
html += '</div>';
|
|
10266
|
-
}
|
|
10267
10603
|
|
|
10268
|
-
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
10604
|
+
// Read-only: Inputs
|
|
10605
|
+
if (def.inputs && Object.keys(def.inputs).length > 0) {
|
|
10606
|
+
html += '<div class="wf-inspector-section"><div class="wf-inspector-section-title">Inputs</div>';
|
|
10607
|
+
for (const [key, spec] of Object.entries(def.inputs)) {
|
|
10608
|
+
const req = spec.required ? ' <span style="color:#e74c3c">*</span>' : '';
|
|
10609
|
+
const defVal = spec.default !== undefined ? ` (default: ${spec.default})` : '';
|
|
10610
|
+
html += `<div style="margin-bottom:8px;">
|
|
10611
|
+
<div style="font-size:12px;font-weight:600;color:var(--text);">${escapeHtml(key)}${req}</div>
|
|
10612
|
+
<div style="font-size:11px;color:var(--text-muted);">${escapeHtml(spec.description || spec.type || '')}${defVal}</div>
|
|
10613
|
+
<input class="wf-inspector-input" id="wf-input-${key}" placeholder="${escapeHtml(key)}" value="${spec.default !== undefined ? spec.default : ''}">
|
|
10614
|
+
</div>`;
|
|
10615
|
+
}
|
|
10616
|
+
html += '</div>';
|
|
10617
|
+
}
|
|
10273
10618
|
|
|
10274
|
-
|
|
10275
|
-
if (def.output) {
|
|
10619
|
+
// Steps summary
|
|
10276
10620
|
html += `<div class="wf-inspector-section">
|
|
10277
|
-
<div class="wf-inspector-section-title">
|
|
10278
|
-
<div
|
|
10621
|
+
<div class="wf-inspector-section-title">Steps</div>
|
|
10622
|
+
<div style="font-size:12px;color:var(--text);">${def.steps.length} step${def.steps.length !== 1 ? 's' : ''}${wfState.layers ? ' in ' + wfState.layers.length + ' layer' + (wfState.layers.length !== 1 ? 's' : '') : ''}</div>
|
|
10279
10623
|
</div>`;
|
|
10624
|
+
|
|
10625
|
+
// Output mapping
|
|
10626
|
+
if (def.output) {
|
|
10627
|
+
html += `<div class="wf-inspector-section">
|
|
10628
|
+
<div class="wf-inspector-section-title">Output</div>
|
|
10629
|
+
<div class="wf-inspector-code">${escapeHtml(JSON.stringify(def.output, null, 2))}</div>
|
|
10630
|
+
</div>`;
|
|
10631
|
+
}
|
|
10280
10632
|
}
|
|
10281
10633
|
|
|
10282
|
-
// Execution result
|
|
10634
|
+
// Execution result (shown in both modes)
|
|
10283
10635
|
if (wfState.executionResults._done) {
|
|
10284
10636
|
const r = wfState.executionResults._done;
|
|
10285
10637
|
const doneJson = JSON.stringify(r.output, null, 2);
|
|
@@ -10305,54 +10657,109 @@ function wfUpdateInspector() {
|
|
|
10305
10657
|
return;
|
|
10306
10658
|
}
|
|
10307
10659
|
|
|
10308
|
-
const meta = WF_NODE_META[step.tool] || { icon:
|
|
10660
|
+
const meta = WF_NODE_META[step.tool] || { icon: WF_FALLBACK_ICON, label: step.tool, color: '#666' };
|
|
10309
10661
|
header.textContent = step.name || step.id;
|
|
10310
10662
|
|
|
10311
10663
|
let html = '';
|
|
10312
10664
|
|
|
10313
|
-
// Tool badge
|
|
10665
|
+
// Tool badge (always shown)
|
|
10314
10666
|
html += `<div class="wf-inspector-section">
|
|
10315
10667
|
<div class="wf-inspector-section-title">Tool</div>
|
|
10316
|
-
<span class="wf-tool-badge" style="background:${meta.color}"
|
|
10668
|
+
<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>
|
|
10317
10669
|
</div>`;
|
|
10318
10670
|
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
|
|
10322
|
-
<div style="font-size:12px;color:var(--text);font-family:monospace;">${escapeHtml(step.id)}</div>
|
|
10323
|
-
</div>`;
|
|
10324
|
-
|
|
10325
|
-
// Inputs
|
|
10326
|
-
if (step.inputs) {
|
|
10671
|
+
if (wfState.builderMode) {
|
|
10672
|
+
// Builder: editable step fields
|
|
10673
|
+
const sid = escapeHtml(step.id);
|
|
10327
10674
|
html += `<div class="wf-inspector-section">
|
|
10328
|
-
<div class="wf-inspector-section-title">
|
|
10329
|
-
|
|
10330
|
-
|
|
10331
|
-
|
|
10332
|
-
|
|
10333
|
-
|
|
10334
|
-
|
|
10675
|
+
<div class="wf-inspector-section-title">Step ID</div>
|
|
10676
|
+
<input class="wf-inspector-input" value="${sid}" style="font-family:monospace;" onchange="wfEditStepId('${sid}', this.value)">
|
|
10677
|
+
</div>`;
|
|
10678
|
+
html += `<div class="wf-inspector-section">
|
|
10679
|
+
<div class="wf-inspector-section-title">Name</div>
|
|
10680
|
+
<input class="wf-inspector-input" value="${escapeHtml(step.name || '')}" onchange="wfEditStepField('${sid}', 'name', this.value)">
|
|
10681
|
+
</div>`;
|
|
10682
|
+
|
|
10683
|
+
// Inputs from WF_INPUT_DEFS
|
|
10684
|
+
const inputDefs = WF_INPUT_DEFS[step.tool] || [];
|
|
10685
|
+
if (inputDefs.length > 0) {
|
|
10686
|
+
html += `<div class="wf-inspector-section"><div class="wf-inspector-section-title">Inputs</div>`;
|
|
10687
|
+
for (const d of inputDefs) {
|
|
10688
|
+
const val = step.inputs?.[d.key] ?? '';
|
|
10689
|
+
const display = typeof val === 'string' ? val : JSON.stringify(val);
|
|
10690
|
+
const req = d.required ? ' <span style="color:#e74c3c;font-size:10px;">required</span>' : '';
|
|
10691
|
+
html += `<div style="margin-bottom:8px;">
|
|
10692
|
+
<div style="font-size:11px;font-weight:600;color:var(--text);">${escapeHtml(d.key)}${req}</div>`;
|
|
10693
|
+
|
|
10694
|
+
if (d.type === 'textarea' || d.type === 'json') {
|
|
10695
|
+
html += `<textarea class="wf-inspector-input" rows="2" style="resize:vertical;font-family:monospace;font-size:11px;" placeholder="${escapeHtml(d.placeholder || '')}" onchange="wfEditStepInput('${sid}','${d.key}',this.value)">${escapeHtml(display)}</textarea>`;
|
|
10696
|
+
} else if (d.type === 'select' && d.options) {
|
|
10697
|
+
html += `<select class="wf-inspector-input" onchange="wfEditStepInput('${sid}','${d.key}',this.value)">
|
|
10698
|
+
<option value="">--</option>`;
|
|
10699
|
+
for (const opt of d.options) {
|
|
10700
|
+
html += `<option value="${escapeHtml(opt)}" ${val === opt ? 'selected' : ''}>${escapeHtml(opt)}</option>`;
|
|
10701
|
+
}
|
|
10702
|
+
html += `</select>`;
|
|
10703
|
+
} else if (d.type === 'number') {
|
|
10704
|
+
html += `<input class="wf-inspector-input" type="number" value="${escapeHtml(String(val))}" placeholder="${escapeHtml(d.placeholder || '')}" onchange="wfEditStepInput('${sid}','${d.key}',this.value)">`;
|
|
10705
|
+
} else {
|
|
10706
|
+
html += `<input class="wf-inspector-input" value="${escapeHtml(display)}" placeholder="${escapeHtml(d.placeholder || '')}" onchange="wfEditStepInput('${sid}','${d.key}',this.value)">`;
|
|
10707
|
+
}
|
|
10708
|
+
html += `</div>`;
|
|
10709
|
+
}
|
|
10710
|
+
html += '</div>';
|
|
10335
10711
|
}
|
|
10336
|
-
html += '</div>';
|
|
10337
|
-
}
|
|
10338
10712
|
|
|
10339
|
-
|
|
10340
|
-
if (step.condition) {
|
|
10713
|
+
// Condition
|
|
10341
10714
|
html += `<div class="wf-inspector-section">
|
|
10342
|
-
<div class="wf-inspector-section-title">Condition
|
|
10343
|
-
<
|
|
10715
|
+
<div class="wf-inspector-section-title">Condition <span style="font-size:10px;color:var(--text-muted)">(optional)</span></div>
|
|
10716
|
+
<input class="wf-inspector-input" value="${escapeHtml(step.condition || '')}" placeholder="e.g. results.length > 0" onchange="wfEditStepField('${sid}', 'condition', this.value || undefined)">
|
|
10344
10717
|
</div>`;
|
|
10345
|
-
}
|
|
10346
10718
|
|
|
10347
|
-
|
|
10348
|
-
|
|
10719
|
+
// continueOnError toggle
|
|
10720
|
+
html += `<div class="wf-inspector-section" style="display:flex;align-items:center;gap:8px;">
|
|
10721
|
+
<input type="checkbox" id="wf-coe-${sid}" ${step.continueOnError ? 'checked' : ''} onchange="wfEditStepField('${sid}', 'continueOnError', this.checked)">
|
|
10722
|
+
<label for="wf-coe-${sid}" style="font-size:11px;color:var(--text);cursor:pointer;">Continue on error</label>
|
|
10723
|
+
</div>`;
|
|
10724
|
+
|
|
10725
|
+
// Delete button
|
|
10726
|
+
html += `<button class="wf-inspector-delete-btn" onclick="if(confirm('Delete step ${sid}?')) wfDeleteStep('${sid}')">Delete Step</button>`;
|
|
10727
|
+
} else {
|
|
10728
|
+
// Read-only step view
|
|
10349
10729
|
html += `<div class="wf-inspector-section">
|
|
10350
|
-
<div class="wf-inspector-section-title">
|
|
10351
|
-
<div
|
|
10730
|
+
<div class="wf-inspector-section-title">ID</div>
|
|
10731
|
+
<div style="font-size:12px;color:var(--text);font-family:monospace;">${escapeHtml(step.id)}</div>
|
|
10352
10732
|
</div>`;
|
|
10733
|
+
|
|
10734
|
+
if (step.inputs) {
|
|
10735
|
+
html += `<div class="wf-inspector-section">
|
|
10736
|
+
<div class="wf-inspector-section-title">Inputs</div>`;
|
|
10737
|
+
for (const [key, val] of Object.entries(step.inputs)) {
|
|
10738
|
+
const display = typeof val === 'string' ? val : JSON.stringify(val);
|
|
10739
|
+
html += `<div class="wf-inspector-field">
|
|
10740
|
+
<span class="wf-inspector-field-label">${escapeHtml(key)}</span>
|
|
10741
|
+
<span class="wf-inspector-field-value" style="font-family:monospace;font-size:11px;">${escapeHtml(display)}</span>
|
|
10742
|
+
</div>`;
|
|
10743
|
+
}
|
|
10744
|
+
html += '</div>';
|
|
10745
|
+
}
|
|
10746
|
+
|
|
10747
|
+
if (step.condition) {
|
|
10748
|
+
html += `<div class="wf-inspector-section">
|
|
10749
|
+
<div class="wf-inspector-section-title">Condition \u26A1</div>
|
|
10750
|
+
<div class="wf-inspector-code">${escapeHtml(step.condition)}</div>
|
|
10751
|
+
</div>`;
|
|
10752
|
+
}
|
|
10753
|
+
|
|
10754
|
+
if (step.forEach) {
|
|
10755
|
+
html += `<div class="wf-inspector-section">
|
|
10756
|
+
<div class="wf-inspector-section-title">ForEach</div>
|
|
10757
|
+
<div class="wf-inspector-code">${escapeHtml(JSON.stringify(step.forEach, null, 2))}</div>
|
|
10758
|
+
</div>`;
|
|
10759
|
+
}
|
|
10353
10760
|
}
|
|
10354
10761
|
|
|
10355
|
-
// Execution result for this step
|
|
10762
|
+
// Execution result for this step (shown in both modes)
|
|
10356
10763
|
const result = wfState.executionResults[step.id];
|
|
10357
10764
|
const state = wfState.executionState[step.id];
|
|
10358
10765
|
if (state === 'completed' && result) {
|
|
@@ -10416,6 +10823,7 @@ function wfSetToolbarEnabled(enabled) {
|
|
|
10416
10823
|
document.getElementById('wfRunBtn').disabled = !enabled;
|
|
10417
10824
|
document.getElementById('wfDryRunBtn').disabled = !enabled;
|
|
10418
10825
|
document.getElementById('wfExportBtn').disabled = !enabled;
|
|
10826
|
+
document.getElementById('wfEditBtn').disabled = !enabled;
|
|
10419
10827
|
}
|
|
10420
10828
|
|
|
10421
10829
|
// ── Export workflow JSON ──
|
|
@@ -10461,9 +10869,9 @@ function wfDryRun() {
|
|
|
10461
10869
|
const step = stepMap[stepId];
|
|
10462
10870
|
if (!step) return;
|
|
10463
10871
|
totalSteps++;
|
|
10464
|
-
const meta = WF_NODE_META[step.tool] || { icon:
|
|
10872
|
+
const meta = WF_NODE_META[step.tool] || { icon: WF_FALLBACK_ICON, label: step.tool, color: '#666' };
|
|
10465
10873
|
layersHtml += '<div class="wf-dryrun-step">';
|
|
10466
|
-
layersHtml += '<span class="wf-dryrun-step-icon"
|
|
10874
|
+
layersHtml += '<span class="wf-dryrun-step-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="' + (meta.icon || WF_FALLBACK_ICON) + '"/></svg></span>';
|
|
10467
10875
|
layersHtml += '<div class="wf-dryrun-step-info">';
|
|
10468
10876
|
layersHtml += '<div class="wf-dryrun-step-name">' + escapeHtml(step.name || step.id) + '</div>';
|
|
10469
10877
|
layersHtml += '<div class="wf-dryrun-step-tool">' + escapeHtml(meta.label) + ' (' + escapeHtml(step.id) + ')</div>';
|
|
@@ -10601,27 +11009,128 @@ function wfStopExecution(reason) {
|
|
|
10601
11009
|
wfUpdateInspector();
|
|
10602
11010
|
}
|
|
10603
11011
|
|
|
10604
|
-
|
|
11012
|
+
// ── Input Modal (pre-execution) ──
|
|
11013
|
+
|
|
11014
|
+
function wfShowInputModal() {
|
|
10605
11015
|
const def = wfState.activeWorkflow;
|
|
10606
|
-
if (!def ||
|
|
11016
|
+
if (!def || !def.inputs) return;
|
|
11017
|
+
const entries = Object.entries(def.inputs);
|
|
11018
|
+
if (entries.length === 0) { wfExecuteWithInputs({}); return; }
|
|
11019
|
+
|
|
11020
|
+
document.getElementById('wfInputModalTitle').textContent = (def.name || 'Workflow') + ' Inputs';
|
|
11021
|
+
let html = '';
|
|
11022
|
+
for (const [key, spec] of entries) {
|
|
11023
|
+
const req = spec.required ? ' <span style="color:#e74c3c">*</span>' : '';
|
|
11024
|
+
const desc = spec.description ? `<div class="wf-input-modal-desc">${escapeHtml(spec.description)}</div>` : '';
|
|
11025
|
+
// Pre-fill from inspector fields if available, then from defaults
|
|
11026
|
+
const inspectorEl = document.getElementById('wf-input-' + key);
|
|
11027
|
+
let prefill = inspectorEl ? inspectorEl.value : '';
|
|
11028
|
+
if (!prefill && spec.default !== undefined) prefill = String(spec.default);
|
|
11029
|
+
const placeholder = spec.type === 'number' ? 'number' : (spec.type || 'string');
|
|
11030
|
+
html += `<div class="wf-input-modal-field">
|
|
11031
|
+
<div class="wf-input-modal-label">${escapeHtml(key)}${req}</div>
|
|
11032
|
+
${desc}
|
|
11033
|
+
<input class="wf-input-modal-input" id="wf-modal-input-${key}" placeholder="${escapeHtml(placeholder)}" value="${escapeHtml(prefill)}" data-key="${escapeHtml(key)}" data-type="${spec.type || 'string'}" data-required="${!!spec.required}">
|
|
11034
|
+
<div class="wf-input-modal-error" id="wf-modal-err-${key}">This field is required</div>
|
|
11035
|
+
</div>`;
|
|
11036
|
+
}
|
|
11037
|
+
document.getElementById('wfInputModalBody').innerHTML = html;
|
|
11038
|
+
document.getElementById('wfInputModalBackdrop').style.display = '';
|
|
11039
|
+
|
|
11040
|
+
// Focus first empty required field, or first field
|
|
11041
|
+
const firstEmpty = entries.find(([k, s]) => {
|
|
11042
|
+
const el = document.getElementById('wf-modal-input-' + k);
|
|
11043
|
+
return s.required && el && !el.value;
|
|
11044
|
+
});
|
|
11045
|
+
const focusKey = firstEmpty ? firstEmpty[0] : entries[0][0];
|
|
11046
|
+
const focusEl = document.getElementById('wf-modal-input-' + focusKey);
|
|
11047
|
+
if (focusEl) setTimeout(() => focusEl.focus(), 50);
|
|
11048
|
+
}
|
|
11049
|
+
|
|
11050
|
+
function wfCloseInputModal() {
|
|
11051
|
+
document.getElementById('wfInputModalBackdrop').style.display = 'none';
|
|
11052
|
+
}
|
|
11053
|
+
|
|
11054
|
+
function wfInputModalSubmit() {
|
|
11055
|
+
const def = wfState.activeWorkflow;
|
|
11056
|
+
if (!def || !def.inputs) return;
|
|
10607
11057
|
|
|
10608
|
-
// Collect inputs
|
|
10609
11058
|
const inputs = {};
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
|
|
10613
|
-
|
|
10614
|
-
|
|
10615
|
-
|
|
10616
|
-
|
|
10617
|
-
|
|
10618
|
-
|
|
10619
|
-
|
|
10620
|
-
|
|
10621
|
-
|
|
11059
|
+
let hasError = false;
|
|
11060
|
+
|
|
11061
|
+
for (const [key, spec] of Object.entries(def.inputs)) {
|
|
11062
|
+
const el = document.getElementById('wf-modal-input-' + key);
|
|
11063
|
+
const errEl = document.getElementById('wf-modal-err-' + key);
|
|
11064
|
+
if (!el) continue;
|
|
11065
|
+
|
|
11066
|
+
let val = el.value.trim();
|
|
11067
|
+
|
|
11068
|
+
// Clear previous error
|
|
11069
|
+
el.classList.remove('error');
|
|
11070
|
+
if (errEl) errEl.style.display = 'none';
|
|
11071
|
+
|
|
11072
|
+
// Required check
|
|
11073
|
+
if (!val && spec.required && spec.default === undefined) {
|
|
11074
|
+
el.classList.add('error');
|
|
11075
|
+
if (errEl) { errEl.textContent = 'This field is required'; errEl.style.display = ''; }
|
|
11076
|
+
hasError = true;
|
|
11077
|
+
continue;
|
|
11078
|
+
}
|
|
11079
|
+
|
|
11080
|
+
// Use default if empty
|
|
11081
|
+
if (!val && spec.default !== undefined) val = String(spec.default);
|
|
11082
|
+
|
|
11083
|
+
// Type validation
|
|
11084
|
+
if (spec.type === 'number' && val && isNaN(Number(val))) {
|
|
11085
|
+
el.classList.add('error');
|
|
11086
|
+
if (errEl) { errEl.textContent = 'Must be a number'; errEl.style.display = ''; }
|
|
11087
|
+
hasError = true;
|
|
11088
|
+
continue;
|
|
10622
11089
|
}
|
|
11090
|
+
|
|
11091
|
+
// Coerce
|
|
11092
|
+
if (spec.type === 'number' && val) val = Number(val);
|
|
11093
|
+
if (val !== undefined && val !== '') inputs[key] = val;
|
|
11094
|
+
}
|
|
11095
|
+
|
|
11096
|
+
if (hasError) return;
|
|
11097
|
+
|
|
11098
|
+
// Also update inspector fields to keep them in sync
|
|
11099
|
+
for (const [key, val] of Object.entries(inputs)) {
|
|
11100
|
+
const inspEl = document.getElementById('wf-input-' + key);
|
|
11101
|
+
if (inspEl) inspEl.value = typeof val === 'number' ? String(val) : val;
|
|
11102
|
+
}
|
|
11103
|
+
|
|
11104
|
+
wfCloseInputModal();
|
|
11105
|
+
wfExecuteWithInputs(inputs);
|
|
11106
|
+
}
|
|
11107
|
+
|
|
11108
|
+
// Keyboard handler for input modal
|
|
11109
|
+
document.addEventListener('keydown', (e) => {
|
|
11110
|
+
const backdrop = document.getElementById('wfInputModalBackdrop');
|
|
11111
|
+
if (!backdrop || backdrop.style.display === 'none') return;
|
|
11112
|
+
if (e.key === 'Escape') { wfCloseInputModal(); e.preventDefault(); }
|
|
11113
|
+
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { wfInputModalSubmit(); e.preventDefault(); }
|
|
11114
|
+
});
|
|
11115
|
+
|
|
11116
|
+
async function wfExecute() {
|
|
11117
|
+
const def = wfState.activeWorkflow;
|
|
11118
|
+
if (!def || wfState.executing) return;
|
|
11119
|
+
|
|
11120
|
+
// If workflow has inputs, show the input modal instead of executing directly
|
|
11121
|
+
if (def.inputs && Object.keys(def.inputs).length > 0) {
|
|
11122
|
+
wfShowInputModal();
|
|
11123
|
+
return;
|
|
10623
11124
|
}
|
|
10624
11125
|
|
|
11126
|
+
// No inputs needed, execute directly
|
|
11127
|
+
wfExecuteWithInputs({});
|
|
11128
|
+
}
|
|
11129
|
+
|
|
11130
|
+
async function wfExecuteWithInputs(inputs) {
|
|
11131
|
+
const def = wfState.activeWorkflow;
|
|
11132
|
+
if (!def || wfState.executing) return;
|
|
11133
|
+
|
|
10625
11134
|
wfState.executing = true;
|
|
10626
11135
|
wfState.executionResults = {};
|
|
10627
11136
|
wfAbortController = new AbortController();
|
|
@@ -10907,7 +11416,7 @@ function wfInitPan() {
|
|
|
10907
11416
|
// Scroll wheel zoom, centered on cursor position
|
|
10908
11417
|
svg.addEventListener('wheel', (e) => {
|
|
10909
11418
|
e.preventDefault();
|
|
10910
|
-
const factor = e.deltaY < 0 ? 1.
|
|
11419
|
+
const factor = e.deltaY < 0 ? 1.03 : 1 / 1.03;
|
|
10911
11420
|
const oldZoom = wfState.zoom;
|
|
10912
11421
|
const newZoom = Math.max(0.2, Math.min(5, oldZoom * factor));
|
|
10913
11422
|
// Zoom toward cursor: keep the SVG point under the mouse fixed
|
|
@@ -10928,6 +11437,446 @@ function wfInitPan() {
|
|
|
10928
11437
|
});
|
|
10929
11438
|
}
|
|
10930
11439
|
|
|
11440
|
+
// ── Builder: Input Definitions ──
|
|
11441
|
+
const WF_INPUT_DEFS = {
|
|
11442
|
+
query: [{ key: 'query', type: 'text', required: true, placeholder: 'Search query' }, { key: 'collection', type: 'text', required: false, placeholder: 'Collection name' }, { key: 'db', type: 'text', required: false, placeholder: 'Database name' }, { key: 'limit', type: 'number', required: false, placeholder: '5' }, { key: 'filter', type: 'json', required: false, placeholder: '{}' }],
|
|
11443
|
+
search: [{ key: 'query', type: 'text', required: true, placeholder: 'Search query' }, { key: 'collection', type: 'text', required: false }, { key: 'db', type: 'text', required: false }, { key: 'limit', type: 'number', required: false, placeholder: '10' }, { key: 'filter', type: 'json', required: false, placeholder: '{}' }],
|
|
11444
|
+
rerank: [{ key: 'query', type: 'text', required: true }, { key: 'documents', type: 'json', required: true, placeholder: '["doc1","doc2"]' }, { key: 'model', type: 'text', required: false, placeholder: 'rerank-2.5' }],
|
|
11445
|
+
ingest: [{ key: 'text', type: 'textarea', required: true }, { key: 'collection', type: 'text', required: false }, { key: 'db', type: 'text', required: false }, { key: 'source', type: 'text', required: false }, { key: 'chunkSize', type: 'number', required: false, placeholder: '512' }, { key: 'chunkStrategy', type: 'select', required: false, options: ['fixed','sentence','paragraph','recursive','markdown'] }],
|
|
11446
|
+
embed: [{ key: 'text', type: 'text', required: true, placeholder: 'Text to embed' }, { key: 'model', type: 'text', required: false, placeholder: 'voyage-3-large' }, { key: 'inputType', type: 'select', required: false, options: ['document','query'] }],
|
|
11447
|
+
similarity: [{ key: 'text1', type: 'text', required: true }, { key: 'text2', type: 'text', required: true }, { key: 'model', type: 'text', required: false }],
|
|
11448
|
+
collections: [{ key: 'db', type: 'text', required: false }],
|
|
11449
|
+
models: [{ key: 'category', type: 'select', required: false, options: ['embedding','rerank','all'] }],
|
|
11450
|
+
estimate: [{ key: 'docs', type: 'number', required: true, placeholder: '1000' }, { key: 'queries', type: 'number', required: false, placeholder: '0' }, { key: 'months', type: 'number', required: false, placeholder: '12' }],
|
|
11451
|
+
explain: [{ key: 'topic', type: 'text', required: true }],
|
|
11452
|
+
topics: [{ key: 'search', type: 'text', required: false }],
|
|
11453
|
+
merge: [{ key: 'sources', type: 'json', required: true, placeholder: '["step1.output","step2.output"]' }, { key: 'strategy', type: 'select', required: false, options: ['concat','interleave','unique'] }],
|
|
11454
|
+
filter: [{ key: 'input', type: 'text', required: true, placeholder: '{{ step.output }}' }, { key: 'condition', type: 'text', required: true, placeholder: 'item.score > 0.5' }],
|
|
11455
|
+
transform: [{ key: 'input', type: 'text', required: true, placeholder: '{{ step.output }}' }, { key: 'expression', type: 'text', required: true, placeholder: 'item.text' }],
|
|
11456
|
+
generate: [{ key: 'prompt', type: 'textarea', required: true, placeholder: 'Generate a summary of...' }, { key: 'context', type: 'text', required: false, placeholder: '{{ step.output }}' }],
|
|
11457
|
+
};
|
|
11458
|
+
|
|
11459
|
+
const WF_CATEGORY_ORDER = ['retrieval', 'embedding', 'management', 'utility', 'control', 'generation'];
|
|
11460
|
+
const WF_CATEGORY_LABELS = { retrieval: 'Retrieval', embedding: 'Embedding', management: 'Management', utility: 'Utility', control: 'Control Flow', generation: 'Generation' };
|
|
11461
|
+
|
|
11462
|
+
// ── Builder: Library/Palette tab toggle ──
|
|
11463
|
+
function wfSwitchLibTab(tab) {
|
|
11464
|
+
const libraryList = document.getElementById('wfLibraryList');
|
|
11465
|
+
const paletteList = document.getElementById('wfPaletteList');
|
|
11466
|
+
document.querySelectorAll('.wf-lib-tab').forEach(b => b.classList.toggle('active', b.dataset.libTab === tab));
|
|
11467
|
+
if (libraryList) libraryList.style.display = tab === 'library' ? '' : 'none';
|
|
11468
|
+
if (paletteList) paletteList.style.display = tab === 'palette' ? '' : 'none';
|
|
11469
|
+
if (tab === 'palette') wfRenderPalette();
|
|
11470
|
+
}
|
|
11471
|
+
|
|
11472
|
+
// ── Builder: Palette rendering ──
|
|
11473
|
+
function wfRenderPalette() {
|
|
11474
|
+
const container = document.getElementById('wfPaletteList');
|
|
11475
|
+
if (!container) return;
|
|
11476
|
+
const grouped = {};
|
|
11477
|
+
for (const [tool, meta] of Object.entries(WF_NODE_META)) {
|
|
11478
|
+
const cat = meta.category || 'unknown';
|
|
11479
|
+
if (!grouped[cat]) grouped[cat] = [];
|
|
11480
|
+
grouped[cat].push({ tool, ...meta });
|
|
11481
|
+
}
|
|
11482
|
+
let html = '';
|
|
11483
|
+
for (const cat of WF_CATEGORY_ORDER) {
|
|
11484
|
+
const items = grouped[cat];
|
|
11485
|
+
if (!items) continue;
|
|
11486
|
+
html += `<div class="wf-palette-category"><div class="wf-palette-category-title">${WF_CATEGORY_LABELS[cat] || cat}</div>`;
|
|
11487
|
+
for (const item of items) {
|
|
11488
|
+
html += `<div class="wf-palette-item" draggable="true" ondragstart="event.dataTransfer.setData('text/plain','${item.tool}')" onclick="wfAddNodeFromPalette('${item.tool}')">
|
|
11489
|
+
<span class="wf-palette-icon" style="color:${item.color}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="${item.icon || WF_FALLBACK_ICON}"/></svg></span>
|
|
11490
|
+
<span class="wf-palette-label">${item.label}</span>
|
|
11491
|
+
</div>`;
|
|
11492
|
+
}
|
|
11493
|
+
html += '</div>';
|
|
11494
|
+
}
|
|
11495
|
+
container.innerHTML = html;
|
|
11496
|
+
}
|
|
11497
|
+
|
|
11498
|
+
// ── Builder: New Workflow ──
|
|
11499
|
+
function wfNewWorkflow() {
|
|
11500
|
+
wfState.activeWorkflow = {
|
|
11501
|
+
name: 'Untitled Workflow',
|
|
11502
|
+
description: '',
|
|
11503
|
+
steps: [],
|
|
11504
|
+
inputs: {},
|
|
11505
|
+
defaults: {},
|
|
11506
|
+
output: {}
|
|
11507
|
+
};
|
|
11508
|
+
wfState.builderMode = true;
|
|
11509
|
+
wfState.dirtyFlag = false;
|
|
11510
|
+
wfState.selectedNodeId = null;
|
|
11511
|
+
wfState.executionState = {};
|
|
11512
|
+
wfState.executionResults = {};
|
|
11513
|
+
wfState.nodePositions = {};
|
|
11514
|
+
wfState.layers = [];
|
|
11515
|
+
wfState.graph = {};
|
|
11516
|
+
wfState.zoom = 1;
|
|
11517
|
+
wfState.panX = 0;
|
|
11518
|
+
wfState.panY = 0;
|
|
11519
|
+
wfState.draggingEdge = null;
|
|
11520
|
+
wfState.dragNode = null;
|
|
11521
|
+
|
|
11522
|
+
// Clear canvas
|
|
11523
|
+
const svg = document.getElementById('wf-canvas');
|
|
11524
|
+
if (svg) svg.querySelectorAll('.wf-node, .wf-edge-group').forEach(el => el.remove());
|
|
11525
|
+
document.getElementById('wfCanvasEmpty').style.display = 'none';
|
|
11526
|
+
svg.style.display = '';
|
|
11527
|
+
|
|
11528
|
+
// Enable toolbar buttons
|
|
11529
|
+
document.getElementById('wfDryRunBtn').disabled = false;
|
|
11530
|
+
document.getElementById('wfRunBtn').disabled = false;
|
|
11531
|
+
document.getElementById('wfExportBtn').disabled = false;
|
|
11532
|
+
|
|
11533
|
+
// Switch to palette tab
|
|
11534
|
+
wfSwitchLibTab('palette');
|
|
11535
|
+
|
|
11536
|
+
// Open inspector for workflow-level editing
|
|
11537
|
+
wfOpenInspector();
|
|
11538
|
+
wfUpdateInspector();
|
|
11539
|
+
|
|
11540
|
+
// Set viewBox
|
|
11541
|
+
wfApplyViewBox();
|
|
11542
|
+
}
|
|
11543
|
+
|
|
11544
|
+
// ── Builder: Edit current workflow ──
|
|
11545
|
+
function wfEditWorkflow() {
|
|
11546
|
+
if (!wfState.activeWorkflow) return;
|
|
11547
|
+
wfState.builderMode = true;
|
|
11548
|
+
wfState.dirtyFlag = false;
|
|
11549
|
+
wfState.selectedNodeId = null;
|
|
11550
|
+
wfState.draggingEdge = null;
|
|
11551
|
+
wfState.dragNode = null;
|
|
11552
|
+
|
|
11553
|
+
// Re-render nodes with builder ports and drag handles
|
|
11554
|
+
wfRefreshNodes();
|
|
11555
|
+
|
|
11556
|
+
// Switch to palette tab and open inspector for workflow-level editing
|
|
11557
|
+
wfSwitchLibTab('palette');
|
|
11558
|
+
wfOpenInspector();
|
|
11559
|
+
wfUpdateInspector();
|
|
11560
|
+
}
|
|
11561
|
+
|
|
11562
|
+
// ── Builder: Add node from palette ──
|
|
11563
|
+
function wfAddNodeFromPalette(tool) {
|
|
11564
|
+
if (!wfState.activeWorkflow) wfNewWorkflow();
|
|
11565
|
+
wfState.builderMode = true;
|
|
11566
|
+
const def = wfState.activeWorkflow;
|
|
11567
|
+
|
|
11568
|
+
// Generate unique step ID
|
|
11569
|
+
let baseId = tool;
|
|
11570
|
+
let id = baseId;
|
|
11571
|
+
let counter = 2;
|
|
11572
|
+
const existingIds = new Set(def.steps.map(s => s.id));
|
|
11573
|
+
while (existingIds.has(id)) { id = baseId + '_' + counter; counter++; }
|
|
11574
|
+
|
|
11575
|
+
// Build default inputs
|
|
11576
|
+
const inputDefs = WF_INPUT_DEFS[tool] || [];
|
|
11577
|
+
const inputs = {};
|
|
11578
|
+
for (const d of inputDefs) {
|
|
11579
|
+
if (d.required) inputs[d.key] = '';
|
|
11580
|
+
}
|
|
11581
|
+
|
|
11582
|
+
const meta = WF_NODE_META[tool] || {};
|
|
11583
|
+
const step = {
|
|
11584
|
+
id,
|
|
11585
|
+
name: meta.label || tool,
|
|
11586
|
+
tool,
|
|
11587
|
+
inputs,
|
|
11588
|
+
};
|
|
11589
|
+
def.steps.push(step);
|
|
11590
|
+
|
|
11591
|
+
// Position: place to the right of all existing nodes
|
|
11592
|
+
let maxX = WF_PAD;
|
|
11593
|
+
for (const pos of Object.values(wfState.nodePositions)) {
|
|
11594
|
+
if (pos.x + WF_NODE_W + WF_LAYER_GAP > maxX) maxX = pos.x + WF_NODE_W + WF_LAYER_GAP;
|
|
11595
|
+
}
|
|
11596
|
+
let y = WF_PAD;
|
|
11597
|
+
// Stack vertically if there are nodes in the same column
|
|
11598
|
+
const nodesAtX = Object.values(wfState.nodePositions).filter(p => Math.abs(p.x - maxX) < 20);
|
|
11599
|
+
if (nodesAtX.length > 0) {
|
|
11600
|
+
y = Math.max(...nodesAtX.map(p => p.y)) + WF_NODE_H + WF_NODE_GAP;
|
|
11601
|
+
}
|
|
11602
|
+
wfState.nodePositions[id] = { x: maxX, y };
|
|
11603
|
+
|
|
11604
|
+
// Rebuild graph
|
|
11605
|
+
wfBuildGraph();
|
|
11606
|
+
wfRefreshNodes();
|
|
11607
|
+
wfSelectNode(id);
|
|
11608
|
+
wfState.dirtyFlag = true;
|
|
11609
|
+
}
|
|
11610
|
+
|
|
11611
|
+
// ── Builder: Build graph from step inputs (template references) ──
|
|
11612
|
+
function wfBuildGraph() {
|
|
11613
|
+
const def = wfState.activeWorkflow;
|
|
11614
|
+
if (!def) return;
|
|
11615
|
+
const graph = {};
|
|
11616
|
+
const stepIds = new Set(def.steps.map(s => s.id));
|
|
11617
|
+
for (const step of def.steps) {
|
|
11618
|
+
const deps = new Set();
|
|
11619
|
+
// Scan all input values for {{ stepId.xxx }} references
|
|
11620
|
+
for (const val of Object.values(step.inputs || {})) {
|
|
11621
|
+
const str = typeof val === 'string' ? val : JSON.stringify(val);
|
|
11622
|
+
const matches = str.matchAll(/\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\./g);
|
|
11623
|
+
for (const m of matches) {
|
|
11624
|
+
if (stepIds.has(m[1]) && m[1] !== step.id) deps.add(m[1]);
|
|
11625
|
+
}
|
|
11626
|
+
}
|
|
11627
|
+
graph[step.id] = Array.from(deps);
|
|
11628
|
+
}
|
|
11629
|
+
wfState.graph = graph;
|
|
11630
|
+
}
|
|
11631
|
+
|
|
11632
|
+
// ── Builder: Canvas drop (from palette drag) ──
|
|
11633
|
+
function wfCanvasDrop(e) {
|
|
11634
|
+
e.preventDefault();
|
|
11635
|
+
const tool = e.dataTransfer.getData('text/plain');
|
|
11636
|
+
if (!tool || !WF_NODE_META[tool]) return;
|
|
11637
|
+
if (!wfState.activeWorkflow) wfNewWorkflow();
|
|
11638
|
+
wfState.builderMode = true;
|
|
11639
|
+
const def = wfState.activeWorkflow;
|
|
11640
|
+
|
|
11641
|
+
// Generate unique ID
|
|
11642
|
+
let id = tool;
|
|
11643
|
+
let counter = 2;
|
|
11644
|
+
const existingIds = new Set(def.steps.map(s => s.id));
|
|
11645
|
+
while (existingIds.has(id)) { id = tool + '_' + counter; counter++; }
|
|
11646
|
+
|
|
11647
|
+
// Inputs
|
|
11648
|
+
const inputDefs = WF_INPUT_DEFS[tool] || [];
|
|
11649
|
+
const inputs = {};
|
|
11650
|
+
for (const d of inputDefs) { if (d.required) inputs[d.key] = ''; }
|
|
11651
|
+
|
|
11652
|
+
const meta = WF_NODE_META[tool] || {};
|
|
11653
|
+
const step = { id, name: meta.label || tool, tool, inputs };
|
|
11654
|
+
def.steps.push(step);
|
|
11655
|
+
|
|
11656
|
+
// Convert drop coordinates to SVG space
|
|
11657
|
+
const svg = document.getElementById('wf-canvas');
|
|
11658
|
+
const rect = svg.getBoundingClientRect();
|
|
11659
|
+
const svgX = (e.clientX - rect.left) / wfState.zoom + wfState.panX;
|
|
11660
|
+
const svgY = (e.clientY - rect.top) / wfState.zoom + wfState.panY;
|
|
11661
|
+
wfState.nodePositions[id] = { x: svgX, y: svgY };
|
|
11662
|
+
|
|
11663
|
+
wfBuildGraph();
|
|
11664
|
+
wfRefreshNodes();
|
|
11665
|
+
wfSelectNode(id);
|
|
11666
|
+
wfState.dirtyFlag = true;
|
|
11667
|
+
}
|
|
11668
|
+
|
|
11669
|
+
// ── Builder: Mutation helpers ──
|
|
11670
|
+
function wfEditStepField(stepId, field, value) {
|
|
11671
|
+
const step = wfState.activeWorkflow?.steps.find(s => s.id === stepId);
|
|
11672
|
+
if (!step) return;
|
|
11673
|
+
step[field] = value;
|
|
11674
|
+
wfState.dirtyFlag = true;
|
|
11675
|
+
if (field === 'name') wfRefreshNodes();
|
|
11676
|
+
}
|
|
11677
|
+
|
|
11678
|
+
function wfEditStepInput(stepId, key, value) {
|
|
11679
|
+
const step = wfState.activeWorkflow?.steps.find(s => s.id === stepId);
|
|
11680
|
+
if (!step) return;
|
|
11681
|
+
if (!step.inputs) step.inputs = {};
|
|
11682
|
+
step.inputs[key] = value;
|
|
11683
|
+
wfState.dirtyFlag = true;
|
|
11684
|
+
// Rebuild graph in case template refs changed
|
|
11685
|
+
wfBuildGraph();
|
|
11686
|
+
wfRefreshNodes();
|
|
11687
|
+
}
|
|
11688
|
+
|
|
11689
|
+
function wfEditStepId(oldId, newId) {
|
|
11690
|
+
const def = wfState.activeWorkflow;
|
|
11691
|
+
if (!def) return;
|
|
11692
|
+
newId = newId.trim().replace(/[^a-zA-Z0-9_]/g, '_');
|
|
11693
|
+
if (!newId || newId === oldId) return;
|
|
11694
|
+
if (def.steps.some(s => s.id === newId)) return; // duplicate
|
|
11695
|
+
|
|
11696
|
+
const step = def.steps.find(s => s.id === oldId);
|
|
11697
|
+
if (!step) return;
|
|
11698
|
+
step.id = newId;
|
|
11699
|
+
|
|
11700
|
+
// Update position map
|
|
11701
|
+
if (wfState.nodePositions[oldId]) {
|
|
11702
|
+
wfState.nodePositions[newId] = wfState.nodePositions[oldId];
|
|
11703
|
+
delete wfState.nodePositions[oldId];
|
|
11704
|
+
}
|
|
11705
|
+
// Update template references in other steps
|
|
11706
|
+
for (const s of def.steps) {
|
|
11707
|
+
for (const [k, v] of Object.entries(s.inputs || {})) {
|
|
11708
|
+
if (typeof v === 'string' && v.includes('{{ ' + oldId + '.')) {
|
|
11709
|
+
s.inputs[k] = v.replaceAll('{{ ' + oldId + '.', '{{ ' + newId + '.');
|
|
11710
|
+
}
|
|
11711
|
+
}
|
|
11712
|
+
}
|
|
11713
|
+
if (wfState.selectedNodeId === oldId) wfState.selectedNodeId = newId;
|
|
11714
|
+
wfState.dirtyFlag = true;
|
|
11715
|
+
wfBuildGraph();
|
|
11716
|
+
wfRefreshNodes();
|
|
11717
|
+
wfUpdateInspector();
|
|
11718
|
+
}
|
|
11719
|
+
|
|
11720
|
+
function wfDeleteStep(stepId) {
|
|
11721
|
+
const def = wfState.activeWorkflow;
|
|
11722
|
+
if (!def) return;
|
|
11723
|
+
def.steps = def.steps.filter(s => s.id !== stepId);
|
|
11724
|
+
delete wfState.nodePositions[stepId];
|
|
11725
|
+
if (wfState.selectedNodeId === stepId) wfState.selectedNodeId = null;
|
|
11726
|
+
wfState.dirtyFlag = true;
|
|
11727
|
+
wfBuildGraph();
|
|
11728
|
+
wfRefreshNodes();
|
|
11729
|
+
wfUpdateInspector();
|
|
11730
|
+
}
|
|
11731
|
+
|
|
11732
|
+
function wfEditWorkflowField(field, value) {
|
|
11733
|
+
if (!wfState.activeWorkflow) return;
|
|
11734
|
+
wfState.activeWorkflow[field] = value;
|
|
11735
|
+
wfState.dirtyFlag = true;
|
|
11736
|
+
}
|
|
11737
|
+
|
|
11738
|
+
// ── Builder: Validate ──
|
|
11739
|
+
async function wfValidateBuilder() {
|
|
11740
|
+
const def = wfState.activeWorkflow;
|
|
11741
|
+
if (!def) return null;
|
|
11742
|
+
try {
|
|
11743
|
+
const res = await fetch('/api/workflows/validate', {
|
|
11744
|
+
method: 'POST',
|
|
11745
|
+
headers: { 'Content-Type': 'application/json' },
|
|
11746
|
+
body: JSON.stringify(def),
|
|
11747
|
+
});
|
|
11748
|
+
return await res.json();
|
|
11749
|
+
} catch (err) {
|
|
11750
|
+
return { valid: false, errors: [err.message] };
|
|
11751
|
+
}
|
|
11752
|
+
}
|
|
11753
|
+
|
|
11754
|
+
// ── Builder: Edge drag ──
|
|
11755
|
+
function wfEdgeDragStart(fromId, fromX, fromY) {
|
|
11756
|
+
const svg = document.getElementById('wf-canvas');
|
|
11757
|
+
if (!svg) return;
|
|
11758
|
+
wfState.draggingEdge = { fromId, fromX, fromY };
|
|
11759
|
+
|
|
11760
|
+
// Create temp edge (dashed bezier), pointer-events: none so it doesn't block port hit-testing
|
|
11761
|
+
const tempPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
11762
|
+
tempPath.setAttribute('id', 'wf-temp-edge');
|
|
11763
|
+
tempPath.setAttribute('fill', 'none');
|
|
11764
|
+
tempPath.setAttribute('stroke', 'var(--accent)');
|
|
11765
|
+
tempPath.setAttribute('stroke-width', '2');
|
|
11766
|
+
tempPath.setAttribute('stroke-dasharray', '6 4');
|
|
11767
|
+
tempPath.setAttribute('pointer-events', 'none');
|
|
11768
|
+
tempPath.setAttribute('d', `M${fromX},${fromY} L${fromX},${fromY}`);
|
|
11769
|
+
svg.appendChild(tempPath);
|
|
11770
|
+
|
|
11771
|
+
function onMove(e) {
|
|
11772
|
+
const rect = svg.getBoundingClientRect();
|
|
11773
|
+
const mx = (e.clientX - rect.left) / wfState.zoom + wfState.panX;
|
|
11774
|
+
const my = (e.clientY - rect.top) / wfState.zoom + wfState.panY;
|
|
11775
|
+
const dx = Math.abs(mx - fromX) * 0.5;
|
|
11776
|
+
tempPath.setAttribute('d', `M${fromX},${fromY} C${fromX + dx},${fromY} ${mx - dx},${my} ${mx},${my}`);
|
|
11777
|
+
}
|
|
11778
|
+
|
|
11779
|
+
function onUp() {
|
|
11780
|
+
document.removeEventListener('mousemove', onMove);
|
|
11781
|
+
document.removeEventListener('mouseup', onUp);
|
|
11782
|
+
const el = document.getElementById('wf-temp-edge');
|
|
11783
|
+
if (el) el.remove();
|
|
11784
|
+
wfState.draggingEdge = null;
|
|
11785
|
+
}
|
|
11786
|
+
|
|
11787
|
+
document.addEventListener('mousemove', onMove);
|
|
11788
|
+
document.addEventListener('mouseup', onUp);
|
|
11789
|
+
}
|
|
11790
|
+
|
|
11791
|
+
function wfEdgeDropOnInput(toId) {
|
|
11792
|
+
if (!wfState.draggingEdge) return;
|
|
11793
|
+
const fromId = wfState.draggingEdge.fromId;
|
|
11794
|
+
if (fromId === toId) return; // no self-connections
|
|
11795
|
+
|
|
11796
|
+
// Add template reference to the target step's first empty required input
|
|
11797
|
+
const def = wfState.activeWorkflow;
|
|
11798
|
+
if (!def) return;
|
|
11799
|
+
const targetStep = def.steps.find(s => s.id === toId);
|
|
11800
|
+
if (!targetStep) return;
|
|
11801
|
+
|
|
11802
|
+
const inputDefs = WF_INPUT_DEFS[targetStep.tool] || [];
|
|
11803
|
+
let connected = false;
|
|
11804
|
+
|
|
11805
|
+
// Try to fill the first empty required input with a template reference
|
|
11806
|
+
for (const d of inputDefs) {
|
|
11807
|
+
if (!targetStep.inputs) targetStep.inputs = {};
|
|
11808
|
+
const current = targetStep.inputs[d.key];
|
|
11809
|
+
if (!current || current === '') {
|
|
11810
|
+
targetStep.inputs[d.key] = `{{ ${fromId}.output }}`;
|
|
11811
|
+
connected = true;
|
|
11812
|
+
break;
|
|
11813
|
+
}
|
|
11814
|
+
}
|
|
11815
|
+
|
|
11816
|
+
// If no empty required input, try first empty optional input
|
|
11817
|
+
if (!connected) {
|
|
11818
|
+
for (const d of inputDefs) {
|
|
11819
|
+
const current = targetStep.inputs?.[d.key];
|
|
11820
|
+
if (!current || current === '') {
|
|
11821
|
+
if (!targetStep.inputs) targetStep.inputs = {};
|
|
11822
|
+
targetStep.inputs[d.key] = `{{ ${fromId}.output }}`;
|
|
11823
|
+
connected = true;
|
|
11824
|
+
break;
|
|
11825
|
+
}
|
|
11826
|
+
}
|
|
11827
|
+
}
|
|
11828
|
+
|
|
11829
|
+
if (connected) {
|
|
11830
|
+
wfState.dirtyFlag = true;
|
|
11831
|
+
wfBuildGraph();
|
|
11832
|
+
wfRelayout();
|
|
11833
|
+
wfRefreshNodes();
|
|
11834
|
+
if (wfState.selectedNodeId === toId) wfUpdateInspector();
|
|
11835
|
+
}
|
|
11836
|
+
|
|
11837
|
+
// Clean up drag state
|
|
11838
|
+
wfState.draggingEdge = null;
|
|
11839
|
+
const el = document.getElementById('wf-temp-edge');
|
|
11840
|
+
if (el) el.remove();
|
|
11841
|
+
}
|
|
11842
|
+
|
|
11843
|
+
// ── Builder: Relayout via topological sort ──
|
|
11844
|
+
async function wfRelayout() {
|
|
11845
|
+
const def = wfState.activeWorkflow;
|
|
11846
|
+
if (!def || def.steps.length === 0) return;
|
|
11847
|
+
|
|
11848
|
+
try {
|
|
11849
|
+
const res = await fetch('/api/workflows/plan', {
|
|
11850
|
+
method: 'POST',
|
|
11851
|
+
headers: { 'Content-Type': 'application/json' },
|
|
11852
|
+
body: JSON.stringify(def),
|
|
11853
|
+
});
|
|
11854
|
+
const data = await res.json();
|
|
11855
|
+
if (data.layers && data.layers.length > 0) {
|
|
11856
|
+
wfState.layers = data.layers;
|
|
11857
|
+
// Reposition nodes based on layers
|
|
11858
|
+
const positions = {};
|
|
11859
|
+
data.layers.forEach((layer, li) => {
|
|
11860
|
+
layer.forEach((stepId, ni) => {
|
|
11861
|
+
positions[stepId] = {
|
|
11862
|
+
x: WF_PAD + li * WF_LAYER_GAP,
|
|
11863
|
+
y: WF_PAD + ni * (WF_NODE_H + WF_NODE_GAP),
|
|
11864
|
+
};
|
|
11865
|
+
});
|
|
11866
|
+
});
|
|
11867
|
+
// Keep orphan nodes (not in any layer) at their current position
|
|
11868
|
+
for (const step of def.steps) {
|
|
11869
|
+
if (!positions[step.id] && wfState.nodePositions[step.id]) {
|
|
11870
|
+
positions[step.id] = wfState.nodePositions[step.id];
|
|
11871
|
+
}
|
|
11872
|
+
}
|
|
11873
|
+
wfState.nodePositions = positions;
|
|
11874
|
+
}
|
|
11875
|
+
} catch (err) {
|
|
11876
|
+
console.warn('Relayout failed:', err.message);
|
|
11877
|
+
}
|
|
11878
|
+
}
|
|
11879
|
+
|
|
10931
11880
|
// ── Docs shortcut (F1) ──
|
|
10932
11881
|
const DOCS_URLS = {
|
|
10933
11882
|
embed: 'https://docs.vaicli.com/docs/commands/embeddings/embed',
|
|
@@ -10956,6 +11905,22 @@ document.addEventListener('keydown', (e) => {
|
|
|
10956
11905
|
document.addEventListener('keydown', (e) => {
|
|
10957
11906
|
const activeTab = document.querySelector('.tab-btn.active');
|
|
10958
11907
|
if (!activeTab || activeTab.dataset.tab !== 'workflows') return;
|
|
11908
|
+
|
|
11909
|
+
// Ctrl/Cmd+S to save/export in builder mode (works even in inputs)
|
|
11910
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 's' && wfState.builderMode) {
|
|
11911
|
+
e.preventDefault();
|
|
11912
|
+
wfExportJson();
|
|
11913
|
+
return;
|
|
11914
|
+
}
|
|
11915
|
+
|
|
11916
|
+
// Delete/Backspace to remove selected node in builder mode
|
|
11917
|
+
if ((e.key === 'Delete' || e.key === 'Backspace') && wfState.builderMode && wfState.selectedNodeId) {
|
|
11918
|
+
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
|
11919
|
+
e.preventDefault();
|
|
11920
|
+
wfDeleteStep(wfState.selectedNodeId);
|
|
11921
|
+
return;
|
|
11922
|
+
}
|
|
11923
|
+
|
|
10959
11924
|
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
|
10960
11925
|
|
|
10961
11926
|
const PAN_STEP = 40;
|