voyageai-cli 1.27.0 → 1.29.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 +2 -1
- package/src/commands/app.js +15 -0
- package/src/commands/config.js +33 -0
- package/src/commands/mcp-server.js +4 -1
- package/src/commands/playground.js +657 -9
- package/src/commands/workflow.js +461 -13
- package/src/lib/api.js +40 -2
- package/src/lib/explanations.js +88 -0
- package/src/lib/npm-utils.js +265 -0
- package/src/lib/workflow-registry.js +416 -0
- package/src/lib/workflow-scaffold.js +319 -0
- package/src/lib/workflow.js +530 -8
- package/src/mcp/server.js +15 -2
- package/src/mcp/sse-transport.js +112 -0
- package/src/playground/announcements.md +71 -0
- package/src/playground/icons/V.png +0 -0
- package/src/playground/index.html +3536 -461
- package/src/workflows/consistency-check.json +4 -0
- package/src/workflows/cost-analysis.json +4 -0
- package/src/workflows/enrich-and-ingest.json +56 -0
- package/src/workflows/intelligent-ingest.json +66 -0
- package/src/workflows/kb-health-report.json +45 -0
- package/src/workflows/multi-collection-search.json +4 -0
- package/src/workflows/research-and-summarize.json +4 -0
- package/src/workflows/search-with-fallback.json +66 -0
- package/src/workflows/smart-ingest.json +4 -0
|
@@ -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;
|
|
@@ -1778,6 +1779,43 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
1778
1779
|
margin-bottom: 8px;
|
|
1779
1780
|
}
|
|
1780
1781
|
.about-text { font-size: 14px; line-height: 1.8; color: var(--text); }
|
|
1782
|
+
.about-changelog { font-size: 13px; line-height: 1.6; }
|
|
1783
|
+
.about-changelog details {
|
|
1784
|
+
border-bottom: 1px solid rgba(255,255,255,0.06);
|
|
1785
|
+
padding: 0;
|
|
1786
|
+
}
|
|
1787
|
+
.about-changelog details:last-child { border-bottom: none; }
|
|
1788
|
+
.about-changelog summary {
|
|
1789
|
+
padding: 10px 0;
|
|
1790
|
+
cursor: pointer;
|
|
1791
|
+
color: var(--text);
|
|
1792
|
+
list-style: none;
|
|
1793
|
+
display: flex;
|
|
1794
|
+
align-items: center;
|
|
1795
|
+
gap: 8px;
|
|
1796
|
+
}
|
|
1797
|
+
.about-changelog summary::before {
|
|
1798
|
+
content: '';
|
|
1799
|
+
display: inline-block;
|
|
1800
|
+
width: 5px; height: 5px;
|
|
1801
|
+
border-right: 1.5px solid var(--accent, #00D4AA);
|
|
1802
|
+
border-bottom: 1.5px solid var(--accent, #00D4AA);
|
|
1803
|
+
transform: rotate(-45deg);
|
|
1804
|
+
transition: transform 0.15s ease;
|
|
1805
|
+
flex-shrink: 0;
|
|
1806
|
+
}
|
|
1807
|
+
.about-changelog details[open] > summary::before {
|
|
1808
|
+
transform: rotate(45deg);
|
|
1809
|
+
}
|
|
1810
|
+
.about-changelog summary::-webkit-details-marker { display: none; }
|
|
1811
|
+
.about-changelog summary:hover { color: var(--accent-text); }
|
|
1812
|
+
.about-changelog p {
|
|
1813
|
+
margin: 0 0 10px 13px;
|
|
1814
|
+
color: var(--text-muted);
|
|
1815
|
+
font-size: 12.5px;
|
|
1816
|
+
line-height: 1.6;
|
|
1817
|
+
}
|
|
1818
|
+
[data-theme="light"] .about-changelog details { border-color: rgba(0,0,0,0.08); }
|
|
1781
1819
|
.about-text a { color: var(--blue); text-decoration: none; }
|
|
1782
1820
|
.about-text a:hover { text-decoration: underline; }
|
|
1783
1821
|
.about-disclaimer {
|
|
@@ -3030,6 +3068,24 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3030
3068
|
font-size: 10px; color: var(--text-muted); margin-top: 4px;
|
|
3031
3069
|
display: flex; gap: 8px;
|
|
3032
3070
|
}
|
|
3071
|
+
/* Collapsible examples section */
|
|
3072
|
+
.wf-library-section { padding: 0; margin-top: 4px; }
|
|
3073
|
+
.wf-library-section-toggle {
|
|
3074
|
+
display: flex; align-items: center; gap: 6px;
|
|
3075
|
+
width: 100%; padding: 8px 12px; border: none; background: none;
|
|
3076
|
+
color: var(--text-muted); font-size: 11px; font-weight: 600;
|
|
3077
|
+
text-transform: uppercase; letter-spacing: 0.5px; cursor: pointer;
|
|
3078
|
+
transition: color 0.15s;
|
|
3079
|
+
}
|
|
3080
|
+
.wf-library-section-toggle:hover { color: var(--text); }
|
|
3081
|
+
.wf-library-section-toggle .arrow {
|
|
3082
|
+
font-size: 8px; transition: transform 0.2s; display: inline-block;
|
|
3083
|
+
}
|
|
3084
|
+
.wf-library-section-toggle.open .arrow { transform: rotate(90deg); }
|
|
3085
|
+
.wf-library-category {
|
|
3086
|
+
font-size: 10px; font-weight: 600; color: var(--text-muted);
|
|
3087
|
+
padding: 8px 12px 2px; text-transform: uppercase; letter-spacing: 0.5px;
|
|
3088
|
+
}
|
|
3033
3089
|
.wf-canvas-area {
|
|
3034
3090
|
flex: 1; position: relative; overflow: hidden;
|
|
3035
3091
|
background: var(--bg);
|
|
@@ -3060,9 +3116,263 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3060
3116
|
}
|
|
3061
3117
|
.wf-canvas-toolbar .wf-run-btn:hover { opacity: 0.9; }
|
|
3062
3118
|
.wf-canvas-toolbar .wf-run-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
3119
|
+
.wf-canvas-toolbar .wf-new-btn {
|
|
3120
|
+
width: auto; padding: 0 12px; gap: 4px;
|
|
3121
|
+
font-weight: 600; font-size: 12px;
|
|
3122
|
+
background: var(--bg-card); border: 1px solid var(--accent);
|
|
3123
|
+
color: var(--accent);
|
|
3124
|
+
}
|
|
3125
|
+
.wf-canvas-toolbar .wf-new-btn:hover { background: var(--accent); color: #fff; }
|
|
3126
|
+
.wf-canvas-toolbar .wf-edit-btn {
|
|
3127
|
+
width: auto; padding: 0 12px; gap: 4px;
|
|
3128
|
+
font-weight: 600; font-size: 12px;
|
|
3129
|
+
background: var(--bg-card); border: 1px solid var(--border);
|
|
3130
|
+
color: var(--text-muted);
|
|
3131
|
+
}
|
|
3132
|
+
.wf-canvas-toolbar .wf-edit-btn:hover { border-color: var(--accent); color: var(--accent); }
|
|
3133
|
+
.wf-canvas-toolbar .wf-edit-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
3134
|
+
.wf-canvas-toolbar .wf-edit-btn:disabled:hover { border-color: var(--border); color: var(--text-muted); }
|
|
3063
3135
|
.wf-toolbar-sep {
|
|
3064
3136
|
width: 1px; height: 20px; background: var(--border); margin: 0 2px;
|
|
3065
3137
|
}
|
|
3138
|
+
/* Library / Palette tabs */
|
|
3139
|
+
.wf-library-tabs {
|
|
3140
|
+
display: flex; gap: 2px; width: 100%;
|
|
3141
|
+
background: var(--bg-input); border-radius: 6px; padding: 2px;
|
|
3142
|
+
}
|
|
3143
|
+
.wf-lib-tab {
|
|
3144
|
+
flex: 1; padding: 5px 0; border: none; border-radius: 4px;
|
|
3145
|
+
background: transparent; color: var(--text-muted); cursor: pointer;
|
|
3146
|
+
font-size: 11px; font-weight: 600; font-family: var(--font);
|
|
3147
|
+
transition: background 0.15s, color 0.15s; text-align: center;
|
|
3148
|
+
}
|
|
3149
|
+
.wf-lib-tab.active { background: var(--accent); color: #fff; }
|
|
3150
|
+
.wf-lib-tab:hover:not(.active) { color: var(--text); }
|
|
3151
|
+
/* Palette items */
|
|
3152
|
+
.wf-palette-category { margin-bottom: 8px; }
|
|
3153
|
+
.wf-palette-category-title {
|
|
3154
|
+
font-size: 10px; font-weight: 700; text-transform: uppercase;
|
|
3155
|
+
letter-spacing: 0.5px; color: var(--text-muted);
|
|
3156
|
+
padding: 4px 12px; margin-bottom: 2px;
|
|
3157
|
+
}
|
|
3158
|
+
.wf-palette-item {
|
|
3159
|
+
display: flex; align-items: center; gap: 8px;
|
|
3160
|
+
padding: 7px 12px; border-radius: 6px; cursor: grab;
|
|
3161
|
+
font-size: 12px; color: var(--text); transition: background 0.15s;
|
|
3162
|
+
}
|
|
3163
|
+
.wf-palette-item:hover { background: var(--bg-card); }
|
|
3164
|
+
.wf-palette-item:active { cursor: grabbing; }
|
|
3165
|
+
.wf-palette-icon { width: 20px; text-align: center; display: flex; align-items: center; justify-content: center; }
|
|
3166
|
+
.wf-palette-label { font-weight: 500; }
|
|
3167
|
+
|
|
3168
|
+
/* ── Workflow Store ── */
|
|
3169
|
+
.wf-store-overlay {
|
|
3170
|
+
position: absolute; inset: 0; background: var(--bg); z-index: 50;
|
|
3171
|
+
display: flex; flex-direction: column;
|
|
3172
|
+
opacity: 0; transform: scale(0.98); pointer-events: none;
|
|
3173
|
+
transition: opacity 0.2s ease, transform 0.2s ease;
|
|
3174
|
+
}
|
|
3175
|
+
.wf-store-overlay.open {
|
|
3176
|
+
opacity: 1; transform: scale(1); pointer-events: all;
|
|
3177
|
+
}
|
|
3178
|
+
.wf-store-overlay::before {
|
|
3179
|
+
content: ''; position: absolute; top: -150px; left: 50%; transform: translateX(-50%);
|
|
3180
|
+
width: 700px; height: 500px;
|
|
3181
|
+
background: radial-gradient(ellipse, rgba(0,212,170,.05) 0%, rgba(64,224,255,.02) 40%, transparent 70%);
|
|
3182
|
+
pointer-events: none; z-index: 0;
|
|
3183
|
+
}
|
|
3184
|
+
.wf-store-header {
|
|
3185
|
+
padding: 14px 20px; border-bottom: 1px solid var(--border);
|
|
3186
|
+
display: flex; align-items: center; gap: 14px; position: relative; z-index: 1; flex-shrink: 0;
|
|
3187
|
+
}
|
|
3188
|
+
.wf-store-back {
|
|
3189
|
+
display: flex; align-items: center; gap: 6px; background: none; border: none;
|
|
3190
|
+
color: var(--text-muted); font-family: var(--font); font-size: 13px; cursor: pointer;
|
|
3191
|
+
padding: 6px 10px; border-radius: 6px; transition: all 0.15s;
|
|
3192
|
+
}
|
|
3193
|
+
.wf-store-back:hover { background: var(--bg-surface); color: var(--accent); }
|
|
3194
|
+
.wf-store-title-area { display: flex; align-items: center; gap: 8px; flex: 1; }
|
|
3195
|
+
.wf-store-title { font-family: var(--mono); font-size: 13px; font-weight: 700; color: var(--text); letter-spacing: 0.02em; }
|
|
3196
|
+
.wf-store-badge { font-family: var(--mono); font-size: 9px; padding: 3px 8px; background: rgba(0,212,170,.15); border: 1px solid rgba(0,212,170,.2); border-radius: 100px; color: var(--accent); letter-spacing: 0.04em; text-transform: uppercase; }
|
|
3197
|
+
.wf-store-search-wrap { position: relative; width: 220px; }
|
|
3198
|
+
.wf-store-search {
|
|
3199
|
+
width: 100%; background: var(--bg-surface); border: 1px solid var(--border); border-radius: 8px;
|
|
3200
|
+
padding: 8px 12px 8px 30px; font-family: var(--font); font-size: 13px; color: var(--text); outline: none;
|
|
3201
|
+
transition: border-color 0.15s;
|
|
3202
|
+
}
|
|
3203
|
+
.wf-store-search:focus { border-color: var(--accent); }
|
|
3204
|
+
.wf-store-search::placeholder { color: var(--text-muted); }
|
|
3205
|
+
.wf-store-search-icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: var(--text-muted); font-size: 13px; pointer-events: none; }
|
|
3206
|
+
.wf-store-sort {
|
|
3207
|
+
background: var(--bg-surface); border: 1px solid var(--border); border-radius: 8px;
|
|
3208
|
+
padding: 8px 10px; font-family: var(--font); font-size: 12px; color: var(--text-muted); outline: none; cursor: pointer;
|
|
3209
|
+
}
|
|
3210
|
+
.wf-store-body { flex: 1; overflow-y: auto; position: relative; z-index: 1; }
|
|
3211
|
+
.wf-store-body::-webkit-scrollbar { width: 6px; }
|
|
3212
|
+
.wf-store-body::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
|
|
3213
|
+
.wf-store-inner { max-width: 1100px; margin: 0 auto; padding: 20px 24px 40px; }
|
|
3214
|
+
.wf-store-chips { display: flex; gap: 5px; flex-wrap: wrap; margin-bottom: 20px; }
|
|
3215
|
+
.wf-store-chip {
|
|
3216
|
+
padding: 6px 14px; background: none; border: 1px solid var(--border); border-radius: 100px;
|
|
3217
|
+
font-family: var(--font); font-size: 12px; color: var(--text-muted); cursor: pointer;
|
|
3218
|
+
transition: all 0.15s; white-space: nowrap;
|
|
3219
|
+
}
|
|
3220
|
+
.wf-store-chip:hover { border-color: var(--text-dim); color: var(--text-dim); }
|
|
3221
|
+
.wf-store-chip.active { background: rgba(0,212,170,.15); border-color: rgba(0,212,170,.3); color: var(--accent); }
|
|
3222
|
+
.wf-store-section-label {
|
|
3223
|
+
font-family: var(--mono); font-size: 10px; letter-spacing: 0.1em;
|
|
3224
|
+
text-transform: uppercase; color: var(--text-muted); margin-bottom: 12px;
|
|
3225
|
+
display: flex; align-items: center; gap: 8px;
|
|
3226
|
+
}
|
|
3227
|
+
.wf-store-count { background: var(--bg-surface); padding: 2px 7px; border-radius: 100px; font-size: 10px; }
|
|
3228
|
+
/* Featured cards */
|
|
3229
|
+
.wf-store-featured-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 24px; }
|
|
3230
|
+
.wf-store-featured-card {
|
|
3231
|
+
border-radius: 14px; padding: 22px 20px 18px; position: relative; overflow: hidden;
|
|
3232
|
+
cursor: pointer; transition: transform 0.2s, box-shadow 0.2s; min-height: 150px;
|
|
3233
|
+
display: flex; flex-direction: column; justify-content: flex-end;
|
|
3234
|
+
}
|
|
3235
|
+
.wf-store-featured-card:hover { transform: translateY(-3px); box-shadow: 0 16px 48px -8px rgba(0,0,0,.5); }
|
|
3236
|
+
.wf-store-featured-card::before {
|
|
3237
|
+
content: ''; position: absolute; inset: 0;
|
|
3238
|
+
background: linear-gradient(180deg, transparent 15%, rgba(0,0,0,.65) 100%); z-index: 1;
|
|
3239
|
+
}
|
|
3240
|
+
.wf-store-featured-content { position: relative; z-index: 2; }
|
|
3241
|
+
.wf-store-featured-content h3 { font-family: var(--mono); font-size: 14px; font-weight: 700; margin-bottom: 4px; color: #fff; }
|
|
3242
|
+
.wf-store-featured-content p { font-size: 12px; color: rgba(255,255,255,.65); line-height: 1.45; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
|
3243
|
+
.wf-store-featured-icon { position: absolute; top: 14px; left: 16px; z-index: 2; width: 36px; height: 36px; border-radius: 10px; background: rgba(255,255,255,.18); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; }
|
|
3244
|
+
.wf-store-featured-icon svg { width: 20px; height: 20px; color: #fff; }
|
|
3245
|
+
.wf-store-featured-dl { position: absolute; top: 12px; right: 14px; z-index: 2; font-family: var(--mono); font-size: 10px; color: rgba(255,255,255,.5); }
|
|
3246
|
+
.wf-store-featured-author { position: relative; z-index: 2; display: flex; align-items: center; gap: 6px; margin-top: 6px; font-size: 11px; color: rgba(255,255,255,.55); }
|
|
3247
|
+
.wf-store-featured-avatar { width: 18px; height: 18px; border-radius: 50%; background: rgba(255,255,255,.2); display: flex; align-items: center; justify-content: center; font-size: 9px; color: #fff; font-weight: 700; overflow: hidden; }
|
|
3248
|
+
.wf-store-featured-avatar img { width: 100%; height: 100%; object-fit: cover; }
|
|
3249
|
+
/* Workflow cards grid */
|
|
3250
|
+
.wf-store-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 10px; }
|
|
3251
|
+
.wf-store-card {
|
|
3252
|
+
background: var(--bg-surface); border: 1px solid var(--border); border-radius: 14px;
|
|
3253
|
+
padding: 16px; cursor: pointer; transition: all 0.15s; position: relative; overflow: hidden;
|
|
3254
|
+
}
|
|
3255
|
+
.wf-store-card:hover { border-color: var(--text-dim); background: var(--bg-card); transform: translateY(-1px); }
|
|
3256
|
+
.wf-store-card-bar { position: absolute; top: 0; left: 0; right: 0; height: 2px; opacity: 0; transition: opacity 0.15s; }
|
|
3257
|
+
.wf-store-card:hover .wf-store-card-bar { opacity: 1; }
|
|
3258
|
+
.wf-store-card-top { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 6px; gap: 8px; }
|
|
3259
|
+
.wf-store-card-icon { width: 28px; height: 28px; border-radius: 8px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
|
3260
|
+
.wf-store-card-icon svg { width: 16px; height: 16px; }
|
|
3261
|
+
.wf-store-card-name-wrap { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
3262
|
+
.wf-store-card-name { font-family: var(--mono); font-size: 13px; font-weight: 700; color: var(--text); }
|
|
3263
|
+
.wf-store-card-badges { display: flex; gap: 4px; }
|
|
3264
|
+
.wf-store-badge-official { font-family: var(--mono); font-size: 9px; padding: 2px 7px; border-radius: 100px; background: rgba(0,212,170,.1); color: var(--accent); border: 1px solid rgba(0,212,170,.15); white-space: nowrap; }
|
|
3265
|
+
.wf-store-badge-installed { font-family: var(--mono); font-size: 9px; padding: 2px 7px; border-radius: 100px; background: rgba(16,185,129,.1); color: #10B981; border: 1px solid rgba(16,185,129,.15); white-space: nowrap; }
|
|
3266
|
+
.wf-store-card-desc { font-size: 12px; color: var(--text-dim); line-height: 1.5; margin-bottom: 4px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
|
3267
|
+
.wf-store-card-author { font-size: 11px; color: var(--text-muted); margin-bottom: 10px; }
|
|
3268
|
+
.wf-store-card-meta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
|
3269
|
+
.wf-store-card-cat { font-family: var(--mono); font-size: 10px; padding: 2px 8px; background: var(--bg-card); border-radius: 100px; color: var(--text-muted); }
|
|
3270
|
+
.wf-store-card-dots { display: flex; gap: 2px; }
|
|
3271
|
+
.wf-store-card-dot { width: 7px; height: 7px; border-radius: 50%; opacity: 0.65; }
|
|
3272
|
+
.wf-store-card-complexity { font-family: var(--mono); font-size: 10px; color: var(--text-muted); }
|
|
3273
|
+
.wf-store-card-downloads { margin-left: auto; font-family: var(--mono); font-size: 10px; color: var(--text-muted); }
|
|
3274
|
+
.wf-store-empty { text-align: center; padding: 40px 0; color: var(--text-muted); }
|
|
3275
|
+
.wf-store-empty p { font-family: var(--mono); font-size: 13px; }
|
|
3276
|
+
/* Detail modal */
|
|
3277
|
+
.wf-store-detail-bg {
|
|
3278
|
+
position: fixed; inset: 0; background: rgba(0,0,0,.55); backdrop-filter: blur(6px);
|
|
3279
|
+
z-index: 200; display: flex; align-items: center; justify-content: center;
|
|
3280
|
+
animation: wfStoreDetailFadeIn 0.12s ease-out; padding: 20px;
|
|
3281
|
+
}
|
|
3282
|
+
@keyframes wfStoreDetailFadeIn { from { opacity: 0; } }
|
|
3283
|
+
@keyframes wfStoreDetailSlideIn { from { transform: translateY(20px); opacity: 0; } }
|
|
3284
|
+
.wf-store-detail-panel {
|
|
3285
|
+
background: var(--bg-surface); border: 1px solid var(--border); border-radius: 18px;
|
|
3286
|
+
width: 100%; max-width: 580px; max-height: 82vh; overflow-y: auto;
|
|
3287
|
+
animation: wfStoreDetailSlideIn 0.18s ease-out;
|
|
3288
|
+
}
|
|
3289
|
+
.wf-store-detail-panel::-webkit-scrollbar { width: 4px; }
|
|
3290
|
+
.wf-store-detail-panel::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
|
|
3291
|
+
.wf-store-detail-hero { padding: 28px 28px 20px; border-radius: 18px 18px 0 0; position: relative; }
|
|
3292
|
+
.wf-store-detail-hero::before { content: ''; position: absolute; inset: 0; background: linear-gradient(180deg, transparent 25%, var(--bg-surface) 100%); border-radius: 18px 18px 0 0; }
|
|
3293
|
+
.wf-store-detail-hero-inner { position: relative; z-index: 1; display: flex; align-items: center; gap: 14px; }
|
|
3294
|
+
.wf-store-detail-hero-icon { width: 48px; height: 48px; border-radius: 12px; background: rgba(255,255,255,.18); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
|
3295
|
+
.wf-store-detail-hero-icon svg { width: 26px; height: 26px; color: #fff; }
|
|
3296
|
+
.wf-store-detail-close {
|
|
3297
|
+
position: absolute; top: 14px; right: 14px; z-index: 2; width: 28px; height: 28px;
|
|
3298
|
+
border-radius: 50%; background: rgba(0,0,0,.35); border: none; color: #fff; font-size: 16px;
|
|
3299
|
+
cursor: pointer; display: flex; align-items: center; justify-content: center;
|
|
3300
|
+
}
|
|
3301
|
+
.wf-store-detail-close:hover { background: rgba(0,0,0,.55); }
|
|
3302
|
+
.wf-store-detail-name { font-family: var(--mono); font-size: 20px; font-weight: 700; color: #fff; margin-bottom: 4px; }
|
|
3303
|
+
.wf-store-detail-pkg { font-family: var(--mono); font-size: 11px; color: rgba(255,255,255,.45); }
|
|
3304
|
+
.wf-store-detail-body { padding: 0 28px 28px; }
|
|
3305
|
+
.wf-store-detail-desc { font-size: 14px; color: var(--text-dim); line-height: 1.65; margin-bottom: 20px; }
|
|
3306
|
+
.wf-store-detail-section { margin-bottom: 16px; }
|
|
3307
|
+
.wf-store-detail-label { font-family: var(--mono); font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-muted); margin-bottom: 8px; }
|
|
3308
|
+
.wf-store-detail-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
|
|
3309
|
+
.wf-store-detail-stat { background: var(--bg-card); border-radius: 8px; padding: 12px; text-align: center; }
|
|
3310
|
+
.wf-store-detail-stat-val { font-family: var(--mono); font-size: 20px; font-weight: 700; color: var(--text); }
|
|
3311
|
+
.wf-store-detail-stat-lbl { font-size: 10px; color: var(--text-muted); margin-top: 2px; }
|
|
3312
|
+
.wf-store-detail-tools { display: flex; gap: 5px; flex-wrap: wrap; }
|
|
3313
|
+
.wf-store-detail-tool { padding: 4px 10px; border-radius: 100px; font-size: 11px; font-family: var(--mono); border: 1px solid; }
|
|
3314
|
+
.wf-store-detail-tags { display: flex; gap: 5px; flex-wrap: wrap; }
|
|
3315
|
+
.wf-store-detail-tag { padding: 4px 10px; background: var(--bg-card); border-radius: 100px; font-size: 11px; color: var(--text-dim); font-family: var(--mono); }
|
|
3316
|
+
.wf-store-detail-install { margin-top: 20px; padding: 16px; background: var(--bg-card); border-radius: 8px; border: 1px solid var(--border); }
|
|
3317
|
+
.wf-store-detail-cmd {
|
|
3318
|
+
font-family: var(--mono); font-size: 12px; color: var(--accent); background: var(--bg);
|
|
3319
|
+
padding: 10px 14px; border-radius: 6px; margin-top: 6px; display: flex; align-items: center;
|
|
3320
|
+
justify-content: space-between; cursor: pointer; border: 1px solid var(--border);
|
|
3321
|
+
transition: border-color 0.15s; overflow-x: auto; white-space: nowrap; gap: 10px;
|
|
3322
|
+
}
|
|
3323
|
+
.wf-store-detail-cmd:hover { border-color: var(--accent); }
|
|
3324
|
+
.wf-store-detail-cmd-hint { font-size: 10px; color: var(--text-muted); flex-shrink: 0; }
|
|
3325
|
+
.wf-store-detail-author { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; }
|
|
3326
|
+
.wf-store-detail-avatar { width: 24px; height: 24px; border-radius: 50%; background: var(--bg-card); display: flex; align-items: center; justify-content: center; font-size: 11px; color: var(--text-muted); font-weight: 700; overflow: hidden; flex-shrink: 0; }
|
|
3327
|
+
.wf-store-detail-avatar img { width: 100%; height: 100%; object-fit: cover; }
|
|
3328
|
+
.wf-store-detail-author-name { font-size: 13px; color: var(--text-dim); }
|
|
3329
|
+
.wf-store-detail-author-name a { color: var(--accent); text-decoration: none; }
|
|
3330
|
+
.wf-store-detail-author-name a:hover { text-decoration: underline; }
|
|
3331
|
+
.wf-store-detail-inputs-table { width: 100%; border-collapse: collapse; font-size: 12px; font-family: var(--mono); }
|
|
3332
|
+
.wf-store-detail-inputs-table th { text-align: left; padding: 6px 10px; color: var(--text-muted); font-weight: 600; font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em; border-bottom: 1px solid var(--border); }
|
|
3333
|
+
.wf-store-detail-inputs-table td { padding: 6px 10px; color: var(--text-dim); border-bottom: 1px solid var(--border); }
|
|
3334
|
+
.wf-store-detail-screenshots { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 6px; }
|
|
3335
|
+
.wf-store-detail-screenshots img { height: 180px; border-radius: 8px; border: 1px solid var(--border); flex-shrink: 0; }
|
|
3336
|
+
.wf-store-detail-actions { display: flex; gap: 8px; margin-top: 16px; }
|
|
3337
|
+
.wf-store-detail-btn {
|
|
3338
|
+
flex: 1; padding: 10px; border-radius: 8px; font-family: var(--mono); font-size: 12px;
|
|
3339
|
+
font-weight: 700; cursor: pointer; border: none; transition: all 0.15s; text-align: center;
|
|
3340
|
+
}
|
|
3341
|
+
.wf-store-detail-btn-primary { background: var(--accent); color: var(--bg); }
|
|
3342
|
+
.wf-store-detail-btn-primary:hover { filter: brightness(1.1); }
|
|
3343
|
+
.wf-store-detail-btn-secondary { background: var(--bg-card); color: var(--text-dim); border: 1px solid var(--border); }
|
|
3344
|
+
.wf-store-detail-btn-secondary:hover { background: var(--border); color: var(--text); }
|
|
3345
|
+
.wf-store-detail-btn-installed { background: rgba(16,185,129,.1); color: #10B981; border: 1px solid rgba(16,185,129,.2); cursor: default; }
|
|
3346
|
+
/* Store button in library header */
|
|
3347
|
+
.wf-store-btn {
|
|
3348
|
+
width: 28px; height: 28px; border-radius: 6px; border: 1px solid transparent;
|
|
3349
|
+
background: none; color: var(--text-muted); font-size: 14px; cursor: pointer;
|
|
3350
|
+
display: flex; align-items: center; justify-content: center; transition: all 0.15s;
|
|
3351
|
+
flex-shrink: 0;
|
|
3352
|
+
}
|
|
3353
|
+
.wf-store-btn:hover { background: var(--bg-card); color: var(--accent); border-color: rgba(0,212,170,.15); }
|
|
3354
|
+
.wf-store-btn.active { color: var(--accent); }
|
|
3355
|
+
@media (max-width: 700px) {
|
|
3356
|
+
.wf-store-featured-grid { grid-template-columns: 1fr; }
|
|
3357
|
+
.wf-store-grid { grid-template-columns: 1fr; }
|
|
3358
|
+
}
|
|
3359
|
+
|
|
3360
|
+
/* Builder port styles */
|
|
3361
|
+
.wf-port-builder { cursor: crosshair; transition: r 0.15s; pointer-events: all !important; }
|
|
3362
|
+
.wf-port-builder:hover { r: 9; filter: brightness(1.4); }
|
|
3363
|
+
.wf-inspector-delete-btn {
|
|
3364
|
+
width: 100%; padding: 8px 0; border: none; border-radius: 6px;
|
|
3365
|
+
background: var(--error); color: #fff; cursor: pointer;
|
|
3366
|
+
font-size: 12px; font-weight: 600; margin-top: 12px;
|
|
3367
|
+
transition: opacity 0.15s;
|
|
3368
|
+
}
|
|
3369
|
+
.wf-inspector-delete-btn:hover { opacity: 0.85; }
|
|
3370
|
+
.wf-validation-errors {
|
|
3371
|
+
background: rgba(255,105,96,0.08); border: 1px solid var(--error);
|
|
3372
|
+
border-radius: 6px; padding: 10px 12px; margin-bottom: 12px;
|
|
3373
|
+
font-size: 11px; color: var(--error);
|
|
3374
|
+
}
|
|
3375
|
+
.wf-validation-errors ul { margin: 4px 0 0 16px; }
|
|
3066
3376
|
/* Dry-run plan overlay */
|
|
3067
3377
|
.wf-dryrun-overlay {
|
|
3068
3378
|
position: absolute; top: 0; left: 0; right: 0; bottom: 0;
|
|
@@ -3107,7 +3417,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3107
3417
|
background: var(--bg-card); border: 1px solid var(--border);
|
|
3108
3418
|
display: flex; align-items: center; gap: 10px; font-size: 12px;
|
|
3109
3419
|
}
|
|
3110
|
-
.wf-dryrun-step-icon {
|
|
3420
|
+
.wf-dryrun-step-icon { flex-shrink: 0; display: flex; align-items: center; }
|
|
3111
3421
|
.wf-dryrun-step-info { flex: 1; min-width: 0; }
|
|
3112
3422
|
.wf-dryrun-step-name { font-weight: 600; color: var(--text); }
|
|
3113
3423
|
.wf-dryrun-step-tool { color: var(--text-muted); font-size: 11px; }
|
|
@@ -3195,8 +3505,61 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3195
3505
|
transition: color 0.15s, border-color 0.15s;
|
|
3196
3506
|
}
|
|
3197
3507
|
.wf-output-expand-btn:hover { color: var(--text); border-color: var(--text-muted); }
|
|
3508
|
+
/* Input modal (pre-execution) */
|
|
3509
|
+
.wf-input-modal-backdrop {
|
|
3510
|
+
position: fixed; inset: 0; z-index: 9999;
|
|
3511
|
+
background: rgba(0,0,0,0.55); display: flex;
|
|
3512
|
+
align-items: center; justify-content: center;
|
|
3513
|
+
}
|
|
3514
|
+
.wf-input-modal {
|
|
3515
|
+
background: var(--bg-surface, var(--bg)); border: 1px solid var(--border);
|
|
3516
|
+
border-radius: 10px; width: 440px; max-width: 90vw;
|
|
3517
|
+
max-height: 80vh; display: flex; flex-direction: column;
|
|
3518
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
|
|
3519
|
+
}
|
|
3520
|
+
.wf-input-modal-header {
|
|
3521
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
3522
|
+
padding: 14px 20px; border-bottom: 1px solid var(--border);
|
|
3523
|
+
}
|
|
3524
|
+
.wf-input-modal-title { font-weight: 700; font-size: 14px; color: var(--text); }
|
|
3525
|
+
.wf-input-modal-body {
|
|
3526
|
+
flex: 1; overflow-y: auto; padding: 16px 20px;
|
|
3527
|
+
}
|
|
3528
|
+
.wf-input-modal-field { margin-bottom: 14px; }
|
|
3529
|
+
.wf-input-modal-label {
|
|
3530
|
+
font-size: 12px; font-weight: 600; color: var(--text); margin-bottom: 4px;
|
|
3531
|
+
}
|
|
3532
|
+
.wf-input-modal-desc {
|
|
3533
|
+
font-size: 11px; color: var(--text-muted); margin-bottom: 4px; line-height: 1.3;
|
|
3534
|
+
}
|
|
3535
|
+
.wf-input-modal-input {
|
|
3536
|
+
width: 100%; box-sizing: border-box; padding: 6px 10px;
|
|
3537
|
+
border: 1px solid var(--border); border-radius: 6px;
|
|
3538
|
+
background: var(--bg); color: var(--text); font-size: 13px;
|
|
3539
|
+
font-family: inherit; outline: none;
|
|
3540
|
+
transition: border-color 0.15s;
|
|
3541
|
+
}
|
|
3542
|
+
.wf-input-modal-input:focus { border-color: var(--accent, #6c63ff); }
|
|
3543
|
+
.wf-input-modal-input.error { border-color: #e74c3c; }
|
|
3544
|
+
.wf-input-modal-error { font-size: 11px; color: #e74c3c; margin-top: 3px; display: none; }
|
|
3545
|
+
.wf-input-modal-footer {
|
|
3546
|
+
display: flex; align-items: center; justify-content: flex-end; gap: 8px;
|
|
3547
|
+
padding: 12px 20px; border-top: 1px solid var(--border);
|
|
3548
|
+
}
|
|
3549
|
+
.wf-input-modal-cancel {
|
|
3550
|
+
padding: 6px 16px; border: 1px solid var(--border); border-radius: 6px;
|
|
3551
|
+
background: transparent; color: var(--text); font-size: 12px; cursor: pointer;
|
|
3552
|
+
transition: background 0.15s, border-color 0.15s;
|
|
3553
|
+
}
|
|
3554
|
+
.wf-input-modal-cancel:hover { background: var(--bg); border-color: var(--text-muted); }
|
|
3555
|
+
.wf-input-modal-run {
|
|
3556
|
+
padding: 6px 16px; border: none; border-radius: 6px;
|
|
3557
|
+
background: var(--accent, #6c63ff); color: #fff; font-size: 12px;
|
|
3558
|
+
font-weight: 600; cursor: pointer; transition: filter 0.15s;
|
|
3559
|
+
}
|
|
3560
|
+
.wf-input-modal-run:hover { filter: brightness(1.15); }
|
|
3198
3561
|
#wf-canvas {
|
|
3199
|
-
width: 100%; height: 100%; display: block;
|
|
3562
|
+
width: 100%; height: 100%; display: block; position: relative; z-index: 1;
|
|
3200
3563
|
}
|
|
3201
3564
|
/* SVG node styles */
|
|
3202
3565
|
.wf-node { cursor: pointer; }
|
|
@@ -3211,8 +3574,8 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3211
3574
|
pointer-events: none; text-anchor: middle; dominant-baseline: central;
|
|
3212
3575
|
}
|
|
3213
3576
|
.wf-node-icon {
|
|
3214
|
-
|
|
3215
|
-
|
|
3577
|
+
pointer-events: none;
|
|
3578
|
+
color: #fff;
|
|
3216
3579
|
}
|
|
3217
3580
|
.wf-node-badge {
|
|
3218
3581
|
font-size: 10px; fill: rgba(255,255,255,0.7);
|
|
@@ -3267,6 +3630,8 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3267
3630
|
}
|
|
3268
3631
|
@keyframes wf-flow { to { stroke-dashoffset: -12; } }
|
|
3269
3632
|
.wf-edge--complete { stroke: var(--accent, #6c63ff); opacity: 0.6; stroke-dasharray: none; }
|
|
3633
|
+
.wf-edge--else { stroke-dasharray: 6 4; opacity: 0.4; }
|
|
3634
|
+
.wf-edge--skipped { opacity: 0.12; stroke-dasharray: 4 4; }
|
|
3270
3635
|
/* Inspector */
|
|
3271
3636
|
.wf-inspector {
|
|
3272
3637
|
flex-shrink: 0; position: relative;
|
|
@@ -3374,7 +3739,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3374
3739
|
}
|
|
3375
3740
|
/* Workflow visualizer light mode */
|
|
3376
3741
|
[data-theme="light"] .wf-node-label { fill: #001E2B; }
|
|
3377
|
-
[data-theme="light"] .wf-node-icon {
|
|
3742
|
+
[data-theme="light"] .wf-node-icon { color: #001E2B; }
|
|
3378
3743
|
[data-theme="light"] .wf-node-badge { fill: rgba(0,30,43,0.55); }
|
|
3379
3744
|
[data-theme="light"] .wf-node-time { fill: rgba(0,30,43,0.5); }
|
|
3380
3745
|
[data-theme="light"] .wf-node-condition { fill: #944F01; }
|
|
@@ -3396,6 +3761,8 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3396
3761
|
[data-theme="light"] .wf-exec-status { box-shadow: 0 2px 8px rgba(0,30,43,0.08); }
|
|
3397
3762
|
[data-theme="light"] .wf-run-btn { background: var(--accent); }
|
|
3398
3763
|
[data-theme="light"] .wf-output-modal { box-shadow: 0 8px 32px rgba(0,30,43,0.15); }
|
|
3764
|
+
[data-theme="light"] .wf-input-modal { box-shadow: 0 8px 32px rgba(0,30,43,0.15); }
|
|
3765
|
+
[data-theme="light"] .wf-input-modal-backdrop { background: rgba(0,0,0,0.3); }
|
|
3399
3766
|
@keyframes wf-pulse-light {
|
|
3400
3767
|
0%, 100% { filter: drop-shadow(0 0 4px rgba(0,158,128,0.3)); }
|
|
3401
3768
|
50% { filter: drop-shadow(0 0 12px rgba(0,158,128,0.6)); }
|
|
@@ -3406,207 +3773,1060 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3406
3773
|
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
|
3407
3774
|
text-align: center; color: var(--text-muted); pointer-events: none;
|
|
3408
3775
|
}
|
|
3409
|
-
.wf-canvas-empty-icon {
|
|
3776
|
+
.wf-canvas-empty-icon { margin-bottom: 16px; opacity: 0.15; }
|
|
3777
|
+
.wf-canvas-empty-icon img { width: 200px; height: 200px; filter: invert(1); }
|
|
3778
|
+
[data-theme="light"] .wf-canvas-empty-icon img { filter: none; opacity: 0.1; }
|
|
3410
3779
|
.wf-canvas-empty-text { font-size: 14px; }
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3780
|
+
/* Persistent watermark behind workflows */
|
|
3781
|
+
.wf-canvas-watermark {
|
|
3782
|
+
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
|
3783
|
+
pointer-events: none; z-index: 0; opacity: 0.04;
|
|
3784
|
+
}
|
|
3785
|
+
.wf-canvas-watermark img { width: 300px; height: 300px; filter: invert(1); }
|
|
3786
|
+
[data-theme="light"] .wf-canvas-watermark img { filter: none; }
|
|
3414
3787
|
|
|
3415
|
-
|
|
3416
|
-
<svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
|
|
3417
|
-
<symbol id="lg-lightning" viewBox="0 0 16 16">
|
|
3418
|
-
<path d="M9.22274 1.99296C9.22274 1.49561 8.56293 1.31233 8.30107 1.73667L4.07384 8.58696C3.87133 8.91513 4.10921 9.33717 4.49748 9.33717H6.77682L6.77682 14.0066C6.77682 14.504 7.43627 14.6879 7.69813 14.2635L11.9262 7.4118C12.1288 7.08363 11.8903 6.66244 11.5021 6.66244H9.22274V1.99296Z" fill="currentColor"/>
|
|
3419
|
-
</symbol>
|
|
3420
|
-
<symbol id="lg-arrows" viewBox="0 0 16 16">
|
|
3421
|
-
<path d="M5 8.57279V13.4272C5 13.9565 4.39241 14.2015 4.0721 13.8014L2.12898 11.3742C1.95701 11.1594 1.95701 10.8406 2.12898 10.6258L4.0721 8.19856C4.39241 7.79846 5 8.04351 5 8.57279Z" fill="currentColor"/>
|
|
3422
|
-
<path d="M5 10H12.5C12.7761 10 13 10.2239 13 10.5V11.5C13 11.7761 12.7761 12 12.5 12H5V10Z" fill="currentColor"/>
|
|
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"/>
|
|
3425
|
-
</symbol>
|
|
3426
|
-
<symbol id="lg-search" viewBox="0 0 16 16">
|
|
3427
|
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.3234 9.81874C4.07618 11.5715 6.75062 11.8398 8.78588 10.6244L12.93 14.7685C13.4377 15.2762 14.2608 15.2762 14.7685 14.7685C15.2762 14.2608 15.2762 13.4377 14.7685 12.93L10.6244 8.78588C11.8398 6.75062 11.5715 4.07619 9.81873 2.32341C7.74896 0.253628 4.39318 0.253628 2.3234 2.32341C0.253624 4.39319 0.253624 7.74896 2.3234 9.81874ZM7.98026 4.16188C9.03467 5.2163 9.03467 6.92585 7.98026 7.98026C6.92584 9.03468 5.2163 9.03468 4.16188 7.98026C3.10746 6.92585 3.10746 5.2163 4.16188 4.16188C5.2163 3.10747 6.92584 3.10747 7.98026 4.16188Z" fill="currentColor"/>
|
|
3428
|
-
</symbol>
|
|
3429
|
-
<symbol id="lg-gauge" viewBox="0 0 16 16">
|
|
3430
|
-
<path d="M1.041 10.2514C0.996713 10.6632 1.33666 11 1.75088 11H4.2449C4.65912 11 4.98569 10.6591 5.08798 10.2577C5.22027 9.73864 5.49013 9.25966 5.87533 8.87446C6.43906 8.31073 7.20364 7.99403 8.00088 7.99403C8.27011 7.99403 8.53562 8.03015 8.79093 8.0997L11.7818 5.10887C10.6623 4.39046 9.35172 4 8.00088 4C6.14436 4 4.36388 4.7375 3.05113 6.05025C1.91604 7.18534 1.21104 8.67012 1.041 10.2514Z" fill="currentColor"/>
|
|
3431
|
-
<path d="M13.2967 6.42237L10.455 9.26409C10.6678 9.56493 10.8231 9.90191 10.9138 10.2577C11.0161 10.6591 11.3426 11 11.7568 11L14.2509 11C14.6651 11 15.005 10.6632 14.9608 10.2514C14.8087 8.83759 14.229 7.50093 13.2967 6.42237Z" fill="currentColor"/>
|
|
3432
|
-
</symbol>
|
|
3433
|
-
<symbol id="lg-bulb" viewBox="0 0 16 16">
|
|
3434
|
-
<path d="M12.3311 8.5C12.7565 7.76457 13 6.91072 13 6C13 3.23858 10.7614 1 8 1C5.23858 1 3 3.23858 3 6C3 6.94628 3.26287 7.83117 3.71958 8.58561L5.40749 11.501C5.58628 11.8099 5.91607 12 6.27291 12H6.5V6C6.5 5.17157 7.17157 4.5 8 4.5C8.82843 4.5 9.5 5.17157 9.5 6V12H9.72368C10.0793 12 10.4082 11.8111 10.5874 11.5039L12.34 8.5H12.3311Z" fill="currentColor"/>
|
|
3435
|
-
<path d="M7.5 6V12H8.5V6C8.5 5.72386 8.27614 5.5 8 5.5C7.72386 5.5 7.5 5.72386 7.5 6Z" fill="currentColor"/>
|
|
3436
|
-
<path d="M10 14V13H6V14C6 14.5523 6.44772 15 7 15H9C9.55228 15 10 14.5523 10 14Z" fill="currentColor"/>
|
|
3437
|
-
</symbol>
|
|
3438
|
-
<symbol id="lg-info" viewBox="0 0 16 16">
|
|
3439
|
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 15C11.866 15 15 11.866 15 8C15 4.13401 11.866 1 8 1C4.13401 1 1 4.13401 1 8C1 11.866 4.13401 15 8 15ZM9 4C9 4.55228 8.55228 5 8 5C7.44772 5 7 4.55228 7 4C7 3.44772 7.44772 3 8 3C8.55228 3 9 3.44772 9 4ZM8 6C8.55228 6 9 6.44772 9 7V11H9.5C9.77614 11 10 11.2239 10 11.5C10 11.7761 9.77614 12 9.5 12H6.5C6.22386 12 6 11.7761 6 11.5C6 11.2239 6.22386 11 6.5 11H7V7H6.5C6.22386 7 6 6.77614 6 6.5C6 6.22386 6.22386 6 6.5 6H8Z" fill="currentColor"/>
|
|
3440
|
-
</symbol>
|
|
3441
|
-
<symbol id="lg-image" viewBox="0 0 16 16">
|
|
3442
|
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 3C2 2.44772 2.44772 2 3 2H13C13.5523 2 14 2.44772 14 3V13C14 13.5523 13.5523 14 13 14H3C2.44772 14 2 13.5523 2 13V3ZM4 4V9.586L5.793 7.793C6.183 7.403 6.817 7.403 7.207 7.793L8.5 9.086L10.293 7.293C10.683 6.903 11.317 6.903 11.707 7.293L12 7.586V4H4ZM12 10.414L10.5 8.914L8.207 11.207C7.817 11.597 7.183 11.597 6.793 11.207L5.5 9.914L4 11.414V12H12V10.414ZM10 5.5C10 6.328 10.672 7 11.5 7C11.776 7 12 6.776 12 6.5V5C12 4.724 11.776 4.5 11.5 4.5H10.5C10.224 4.5 10 4.724 10 5V5.5Z" fill="currentColor"/>
|
|
3443
|
-
</symbol>
|
|
3444
|
-
<symbol id="lg-config" viewBox="0 0 16 16">
|
|
3445
|
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.5 1.5A.5.5 0 0 1 7 1h2a.5.5 0 0 1 .5.5v1.05a5 5 0 0 1 1.37.57l.74-.74a.5.5 0 0 1 .7 0l1.42 1.42a.5.5 0 0 1 0 .7l-.74.74c.25.43.44.89.57 1.37H14.5a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5h-1.05a5 5 0 0 1-.57 1.37l.74.74a.5.5 0 0 1 0 .7l-1.42 1.42a.5.5 0 0 1-.7 0l-.74-.74a5 5 0 0 1-1.37.57V14.5a.5.5 0 0 1-.5.5H7a.5.5 0 0 1-.5-.5v-1.05a5 5 0 0 1-1.37-.57l-.74.74a.5.5 0 0 1-.7 0L2.27 12.2a.5.5 0 0 1 0-.7l.74-.74a5 5 0 0 1-.57-1.37H1.5A.5.5 0 0 1 1 9V7a.5.5 0 0 1 .5-.5h1.05c.13-.48.32-.94.57-1.37l-.74-.74a.5.5 0 0 1 0-.7L3.8 2.27a.5.5 0 0 1 .7 0l.74.74A5 5 0 0 1 6.6 2.44V1.5zM8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5z" fill="currentColor"/>
|
|
3446
|
-
</symbol>
|
|
3447
|
-
<symbol id="lg-code" viewBox="0 0 16 16">
|
|
3448
|
-
<path fill="currentColor" d="M5.854 4.146a.5.5 0 0 1 0 .708L2.707 8l3.147 3.146a.5.5 0 0 1-.708.708l-3.5-3.5a.5.5 0 0 1 0-.708l3.5-3.5a.5.5 0 0 1 .708 0zm4.292 0a.5.5 0 0 0 0 .708L13.293 8l-3.147 3.146a.5.5 0 0 0 .708.708l3.5-3.5a.5.5 0 0 0 0-.708l-3.5-3.5a.5.5 0 0 0-.708 0z"/>
|
|
3449
|
-
</symbol>
|
|
3450
|
-
<symbol id="lg-palette" viewBox="0 0 16 16">
|
|
3451
|
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 1C4.13401 1 1 4.13401 1 8C1 11.866 4.13401 15 8 15C8.55228 15 9 14.5523 9 14V12.5C9 11.6716 9.67157 11 10.5 11H12C13.6569 11 15 9.65685 15 8C15 4.13401 11.866 1 8 1ZM4.5 9C5.32843 9 6 8.32843 6 7.5C6 6.67157 5.32843 6 4.5 6C3.67157 6 3 6.67157 3 7.5C3 8.32843 3.67157 9 4.5 9ZM7 5.5C7 6.32843 6.32843 7 5.5 7C4.67157 7 4 6.32843 4 5.5C4 4.67157 4.67157 4 5.5 4C6.32843 4 7 4.67157 7 5.5ZM9.5 6C10.3284 6 11 5.32843 11 4.5C11 3.67157 10.3284 3 9.5 3C8.67157 3 8 3.67157 8 4.5C8 5.32843 8.67157 6 9.5 6ZM13 7.5C13 8.32843 12.3284 9 11.5 9C10.6716 9 10 8.32843 10 7.5C10 6.67157 10.6716 6 11.5 6C12.3284 6 13 6.67157 13 7.5Z" fill="currentColor"/>
|
|
3452
|
-
</symbol>
|
|
3453
|
-
<symbol id="lg-cube" viewBox="0 0 16 16">
|
|
3454
|
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.35 1.18a.75.75 0 0 0-.7 0l-6 3.25A.75.75 0 0 0 1.25 5v6a.75.75 0 0 0 .4.66l6 3.25a.75.75 0 0 0 .7 0l6-3.25a.75.75 0 0 0 .4-.66V5a.75.75 0 0 0-.4-.66l-6-3.16zM8 3.2 3.47 5.65 8 7.82l4.53-2.17L8 3.2zM2.75 6.8v3.82L7.25 12.8V8.97L2.75 6.8zm5.5 6 4.5-2.18V6.8L8.25 8.97V12.8z" fill="currentColor"/>
|
|
3455
|
-
</symbol>
|
|
3456
|
-
<symbol id="lg-chat" viewBox="0 0 16 16">
|
|
3457
|
-
<path d="M2 2h12v9H5l-3 3V2z" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
|
3458
|
-
</symbol>
|
|
3459
|
-
<symbol id="lg-shield" viewBox="0 0 16 16">
|
|
3460
|
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.35 1.18a.75.75 0 0 0-.7 0l-5 2.7A.75.75 0 0 0 2.25 4.5V8c0 2.9 2.1 5.5 5.5 6.95a.75.75 0 0 0 .5 0C11.65 13.5 13.75 10.9 13.75 8V4.5a.75.75 0 0 0-.4-.62l-5-2.7zM8 3.2 3.75 5.5V8c0 2.2 1.6 4.2 4.25 5.45C10.65 12.2 12.25 10.2 12.25 8V5.5L8 3.2z" fill="currentColor"/>
|
|
3461
|
-
</symbol>
|
|
3462
|
-
<symbol id="lg-pulse" viewBox="0 0 16 16">
|
|
3463
|
-
<path d="M1 8h3l2-5 2 10 2-5h5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
3464
|
-
</symbol>
|
|
3465
|
-
</svg>
|
|
3788
|
+
/* ── Home Page Styles ── */
|
|
3466
3789
|
|
|
3467
|
-
|
|
3790
|
+
/* Home Header */
|
|
3791
|
+
.home-header {
|
|
3792
|
+
display: flex;
|
|
3793
|
+
align-items: center;
|
|
3794
|
+
justify-content: space-between;
|
|
3795
|
+
padding: 24px 32px 16px;
|
|
3796
|
+
border-bottom: 1px solid var(--border);
|
|
3797
|
+
background: var(--bg);
|
|
3798
|
+
}
|
|
3468
3799
|
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
<button class="sidebar-settings-btn" data-tab="settings" title="Settings"><svg width="16" height="16" viewBox="0 0 16 16"><use href="#lg-config"/></svg></button>
|
|
3475
|
-
</div>
|
|
3476
|
-
<nav class="sidebar-nav">
|
|
3477
|
-
<div class="sidebar-nav-group" role="tablist" aria-label="Tools">
|
|
3478
|
-
<div class="sidebar-nav-label" id="nav-tools-label">Tools</div>
|
|
3479
|
-
<button class="tab-btn active" data-tab="embed" role="tab" aria-selected="true" aria-controls="tab-embed" id="tab-btn-embed"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-lightning"/></svg></span><span>Embed</span></button>
|
|
3480
|
-
<button class="tab-btn" data-tab="compare" role="tab" aria-selected="false" aria-controls="tab-compare" id="tab-btn-compare"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-arrows"/></svg></span><span>Compare</span></button>
|
|
3481
|
-
<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
|
-
<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
|
-
<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 width="16" height="16" viewBox="0 0 16 16"><path d="M2 2h12v9H5l-3 3V2z" fill="none" stroke="currentColor" stroke-width="1.5"/></svg></span><span>Chat</span></button>
|
|
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 width="16" height="16" viewBox="0 0 16 16"><circle cx="3" cy="8" r="2" fill="none" stroke="currentColor" stroke-width="1.3"/><circle cx="13" cy="4" r="2" fill="none" stroke="currentColor" stroke-width="1.3"/><circle cx="13" cy="12" r="2" fill="none" stroke="currentColor" stroke-width="1.3"/><line x1="5" y1="7" x2="11" y2="4.5" stroke="currentColor" stroke-width="1.3"/><line x1="5" y1="9" x2="11" y2="11.5" stroke="currentColor" stroke-width="1.3"/></svg></span><span>Workflows</span></button>
|
|
3486
|
-
</div>
|
|
3487
|
-
<div class="sidebar-nav-divider"></div>
|
|
3488
|
-
<div class="sidebar-nav-group" role="tablist" aria-label="Learn">
|
|
3489
|
-
<div class="sidebar-nav-label" id="nav-learn-label">Learn</div>
|
|
3490
|
-
<button class="tab-btn" data-tab="benchmark" role="tab" aria-selected="false" aria-controls="tab-benchmark" id="tab-btn-benchmark"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-gauge"/></svg></span><span>Benchmark</span></button>
|
|
3491
|
-
<button class="tab-btn" data-tab="explore" role="tab" aria-selected="false" aria-controls="tab-explore" id="tab-btn-explore"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-bulb"/></svg></span><span>Explore</span></button>
|
|
3492
|
-
<button class="tab-btn" data-tab="about" role="tab" aria-selected="false" aria-controls="tab-about" id="tab-btn-about"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-info"/></svg></span><span>About</span></button>
|
|
3493
|
-
</div>
|
|
3494
|
-
</nav>
|
|
3495
|
-
<div class="sidebar-footer">
|
|
3496
|
-
<div style="display:flex;align-items:center;gap:8px;justify-content:space-between;">
|
|
3497
|
-
<div style="display:flex;align-items:center;gap:5px;">
|
|
3498
|
-
<div class="status-dot" id="statusDot"></div>
|
|
3499
|
-
<span class="status-label" id="statusLabel">Checking...</span>
|
|
3500
|
-
</div>
|
|
3501
|
-
<button class="theme-toggle" id="themeToggle" title="Toggle light/dark mode">🌙</button>
|
|
3502
|
-
</div>
|
|
3503
|
-
<div style="display:flex;align-items:center;justify-content:space-between;">
|
|
3504
|
-
<div id="appVersionLabel" style="font-size:10px;color:var(--text-muted);"></div>
|
|
3505
|
-
<div style="display:flex;align-items:center;gap:6px;">
|
|
3506
|
-
<a class="sidebar-docs-link" href="https://docs.vaicli.com" target="_blank" rel="noopener" title="Documentation (F1)">
|
|
3507
|
-
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 1h7l3 3v11H3z"/><path d="M10 1v3h3"/><path d="M6 8h4M6 10.5h4"/></svg>
|
|
3508
|
-
<span>Docs</span>
|
|
3509
|
-
</a>
|
|
3510
|
-
<button class="sidebar-bug-link" id="bugButton" title="Report a Bug">
|
|
3511
|
-
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="9" r="4.5"/><path d="M5.5 5.5L3 3M10.5 5.5L13 3M3 9H1M15 9h-2M5.5 12.5L4 15M10.5 12.5L12 15"/></svg>
|
|
3512
|
-
<span class="sidebar-bug-label">Bug</span>
|
|
3513
|
-
</button>
|
|
3514
|
-
</div>
|
|
3515
|
-
</div>
|
|
3516
|
-
</div>
|
|
3517
|
-
</aside>
|
|
3800
|
+
.home-header-left {
|
|
3801
|
+
display: flex;
|
|
3802
|
+
align-items: center;
|
|
3803
|
+
gap: 12px;
|
|
3804
|
+
}
|
|
3518
3805
|
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
<span class="update-banner-icon">🚀</span>
|
|
3524
|
-
<span class="update-banner-text" id="updateBannerText">
|
|
3525
|
-
<strong>Vai <span id="updateVersion"></span></strong> is available.
|
|
3526
|
-
You're on <span id="currentVersion"></span>.
|
|
3527
|
-
</span>
|
|
3528
|
-
<div class="update-progress" id="updateProgress" style="display:none;">
|
|
3529
|
-
<div class="update-progress-bar"><div class="update-progress-fill" id="updateProgressFill"></div></div>
|
|
3530
|
-
<span class="update-progress-label" id="updateProgressLabel">0%</span>
|
|
3531
|
-
</div>
|
|
3532
|
-
<button class="update-banner-btn" id="updateDownloadBtn">Download & Install</button>
|
|
3533
|
-
<button class="update-banner-btn" id="updateInstallBtn" style="display:none;">Restart & Update</button>
|
|
3534
|
-
<button class="update-banner-dismiss" id="updateDismissBtn" title="Dismiss">×</button>
|
|
3535
|
-
</div>
|
|
3536
|
-
<div class="main">
|
|
3806
|
+
.home-logo {
|
|
3807
|
+
width: 32px;
|
|
3808
|
+
height: 32px;
|
|
3809
|
+
}
|
|
3537
3810
|
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
<a class="page-header-docs" href="https://docs.vaicli.com/docs/commands/embeddings/embed" target="_blank" rel="noopener" title="Embed documentation"><svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 1h7l3 3v11H3z"/><path d="M10 1v3h3"/></svg>Docs</a>
|
|
3545
|
-
</div>
|
|
3546
|
-
<div class="card">
|
|
3547
|
-
<div class="card-title">Input Text</div>
|
|
3548
|
-
<textarea id="embedInput" rows="5" placeholder="Enter text to embed..." aria-label="Text to embed">MongoDB Atlas provides powerful vector search capabilities for AI applications.</textarea>
|
|
3549
|
-
</div>
|
|
3811
|
+
.home-header-title {
|
|
3812
|
+
font-size: 16px;
|
|
3813
|
+
font-weight: 600;
|
|
3814
|
+
color: var(--text);
|
|
3815
|
+
letter-spacing: -0.01em;
|
|
3816
|
+
}
|
|
3550
3817
|
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
<option value="query">Query</option>
|
|
3561
|
-
<option value="document">Document</option>
|
|
3562
|
-
</select>
|
|
3563
|
-
</div>
|
|
3564
|
-
<div class="option-group">
|
|
3565
|
-
<span class="option-label">Dimensions</span>
|
|
3566
|
-
<select id="embedDimensions">
|
|
3567
|
-
<option value="">Default</option>
|
|
3568
|
-
<option value="256">256</option>
|
|
3569
|
-
<option value="512">512</option>
|
|
3570
|
-
<option value="1024">1024</option>
|
|
3571
|
-
<option value="2048">2048</option>
|
|
3572
|
-
</select>
|
|
3573
|
-
</div>
|
|
3574
|
-
<div class="option-group">
|
|
3575
|
-
<span class="option-label">Output Type</span>
|
|
3576
|
-
<select id="embedOutputDtype">
|
|
3577
|
-
<option value="float">float (32-bit)</option>
|
|
3578
|
-
<option value="int8">int8 (4× smaller)</option>
|
|
3579
|
-
<option value="uint8">uint8 (4× smaller)</option>
|
|
3580
|
-
<option value="binary">binary (32× smaller)</option>
|
|
3581
|
-
<option value="ubinary">ubinary (32× smaller)</option>
|
|
3582
|
-
</select>
|
|
3583
|
-
</div>
|
|
3584
|
-
<button class="btn" id="embedBtn" onclick="doEmbed()">⚡ Embed</button>
|
|
3585
|
-
</div>
|
|
3818
|
+
.home-version-badge {
|
|
3819
|
+
background: var(--bg-card);
|
|
3820
|
+
border: 1px solid var(--border);
|
|
3821
|
+
color: var(--text-dim);
|
|
3822
|
+
padding: 4px 8px;
|
|
3823
|
+
border-radius: 12px;
|
|
3824
|
+
font-size: 11px;
|
|
3825
|
+
font-weight: 500;
|
|
3826
|
+
}
|
|
3586
3827
|
|
|
3587
|
-
|
|
3828
|
+
.home-header-right {
|
|
3829
|
+
display: flex;
|
|
3830
|
+
align-items: center;
|
|
3831
|
+
gap: 12px;
|
|
3832
|
+
}
|
|
3588
3833
|
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
<div class="heatmap" id="embedHeatmap"></div>
|
|
3599
|
-
</div>
|
|
3600
|
-
</div>
|
|
3601
|
-
</div>
|
|
3834
|
+
.home-settings-btn {
|
|
3835
|
+
background: none;
|
|
3836
|
+
border: 1px solid var(--border);
|
|
3837
|
+
color: var(--text-dim);
|
|
3838
|
+
padding: 8px;
|
|
3839
|
+
border-radius: 8px;
|
|
3840
|
+
cursor: pointer;
|
|
3841
|
+
transition: all 0.2s;
|
|
3842
|
+
}
|
|
3602
3843
|
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3844
|
+
.home-settings-btn:hover {
|
|
3845
|
+
background: var(--bg-card);
|
|
3846
|
+
color: var(--text);
|
|
3847
|
+
border-color: var(--accent);
|
|
3848
|
+
}
|
|
3849
|
+
|
|
3850
|
+
.home-settings-btn svg {
|
|
3851
|
+
width: 16px;
|
|
3852
|
+
height: 16px;
|
|
3853
|
+
}
|
|
3854
|
+
|
|
3855
|
+
/* API Key Warning Banner */
|
|
3856
|
+
.home-api-warning {
|
|
3857
|
+
margin: 16px 32px;
|
|
3858
|
+
background: linear-gradient(135deg, rgba(255, 201, 16, 0.1), rgba(255, 105, 96, 0.1));
|
|
3859
|
+
border: 1px solid rgba(255, 201, 16, 0.3);
|
|
3860
|
+
border-radius: 12px;
|
|
3861
|
+
padding: 16px;
|
|
3862
|
+
animation: slideDown 0.3s ease-out;
|
|
3863
|
+
}
|
|
3864
|
+
|
|
3865
|
+
.home-api-warning-content {
|
|
3866
|
+
display: flex;
|
|
3867
|
+
align-items: center;
|
|
3868
|
+
gap: 12px;
|
|
3869
|
+
color: var(--warning);
|
|
3870
|
+
}
|
|
3871
|
+
|
|
3872
|
+
.home-api-warning svg {
|
|
3873
|
+
width: 20px;
|
|
3874
|
+
height: 20px;
|
|
3875
|
+
flex-shrink: 0;
|
|
3876
|
+
}
|
|
3877
|
+
|
|
3878
|
+
.home-api-warning button {
|
|
3879
|
+
margin-left: auto;
|
|
3880
|
+
background: none;
|
|
3881
|
+
border: 1px solid var(--warning);
|
|
3882
|
+
color: var(--warning);
|
|
3883
|
+
padding: 6px 12px;
|
|
3884
|
+
border-radius: 6px;
|
|
3885
|
+
cursor: pointer;
|
|
3886
|
+
font-size: 12px;
|
|
3887
|
+
transition: all 0.2s;
|
|
3888
|
+
}
|
|
3889
|
+
|
|
3890
|
+
.home-api-warning button:hover {
|
|
3891
|
+
background: var(--warning);
|
|
3892
|
+
color: var(--bg);
|
|
3893
|
+
}
|
|
3894
|
+
|
|
3895
|
+
/* Home Content */
|
|
3896
|
+
.home-content {
|
|
3897
|
+
padding: 32px;
|
|
3898
|
+
max-width: 1200px;
|
|
3899
|
+
margin: 0 auto;
|
|
3900
|
+
overflow-y: auto;
|
|
3901
|
+
height: calc(100vh - 120px);
|
|
3902
|
+
}
|
|
3903
|
+
|
|
3904
|
+
/* Announcements Banner */
|
|
3905
|
+
.home-announcements {
|
|
3906
|
+
margin-bottom: 48px;
|
|
3907
|
+
animation: fadeInUp 0.6s ease-out;
|
|
3908
|
+
}
|
|
3909
|
+
|
|
3910
|
+
.home-announcements-carousel {
|
|
3911
|
+
position: relative;
|
|
3912
|
+
overflow: hidden;
|
|
3913
|
+
border-radius: 16px;
|
|
3914
|
+
background: linear-gradient(135deg, var(--bg-card), var(--bg-surface));
|
|
3915
|
+
border: 1px solid var(--border);
|
|
3916
|
+
}
|
|
3917
|
+
|
|
3918
|
+
.home-announcement-card {
|
|
3919
|
+
display: none;
|
|
3920
|
+
padding: 32px;
|
|
3921
|
+
text-align: center;
|
|
3922
|
+
position: relative;
|
|
3923
|
+
}
|
|
3924
|
+
|
|
3925
|
+
.home-announcement-card.active {
|
|
3926
|
+
display: block;
|
|
3927
|
+
animation: fadeIn 0.5s ease-out;
|
|
3928
|
+
}
|
|
3929
|
+
|
|
3930
|
+
.home-announcement-card .badge {
|
|
3931
|
+
display: inline-block;
|
|
3932
|
+
background: linear-gradient(135deg, #00ED64, #00C2FF);
|
|
3933
|
+
color: white;
|
|
3934
|
+
padding: 4px 12px;
|
|
3935
|
+
border-radius: 12px;
|
|
3936
|
+
font-size: 11px;
|
|
3937
|
+
font-weight: 600;
|
|
3938
|
+
margin-bottom: 16px;
|
|
3939
|
+
text-transform: uppercase;
|
|
3940
|
+
letter-spacing: 0.5px;
|
|
3941
|
+
}
|
|
3942
|
+
|
|
3943
|
+
.home-announcement-card h3 {
|
|
3944
|
+
font-size: 24px;
|
|
3945
|
+
font-weight: 700;
|
|
3946
|
+
color: var(--accent-text);
|
|
3947
|
+
margin-bottom: 12px;
|
|
3948
|
+
}
|
|
3949
|
+
|
|
3950
|
+
.home-announcement-card p {
|
|
3951
|
+
color: var(--text-dim);
|
|
3952
|
+
line-height: 1.6;
|
|
3953
|
+
margin-bottom: 24px;
|
|
3954
|
+
max-width: 600px;
|
|
3955
|
+
margin-left: auto;
|
|
3956
|
+
margin-right: auto;
|
|
3957
|
+
}
|
|
3958
|
+
|
|
3959
|
+
.home-announcement-card .cta {
|
|
3960
|
+
background: linear-gradient(135deg, #00ED64, #00C2FF);
|
|
3961
|
+
color: white;
|
|
3962
|
+
border: none;
|
|
3963
|
+
padding: 12px 24px;
|
|
3964
|
+
border-radius: 8px;
|
|
3965
|
+
font-weight: 600;
|
|
3966
|
+
cursor: pointer;
|
|
3967
|
+
transition: all 0.2s;
|
|
3968
|
+
}
|
|
3969
|
+
|
|
3970
|
+
.home-announcement-card .cta:hover {
|
|
3971
|
+
transform: translateY(-2px);
|
|
3972
|
+
box-shadow: 0 8px 32px rgba(0, 237, 100, 0.3);
|
|
3973
|
+
}
|
|
3974
|
+
|
|
3975
|
+
.home-announcement-dismiss {
|
|
3976
|
+
position: absolute;
|
|
3977
|
+
top: 16px;
|
|
3978
|
+
right: 16px;
|
|
3979
|
+
background: none;
|
|
3980
|
+
border: none;
|
|
3981
|
+
color: var(--text-muted);
|
|
3982
|
+
cursor: pointer;
|
|
3983
|
+
padding: 4px;
|
|
3984
|
+
border-radius: 4px;
|
|
3985
|
+
transition: all 0.2s;
|
|
3986
|
+
}
|
|
3987
|
+
|
|
3988
|
+
.home-announcement-dismiss:hover {
|
|
3989
|
+
background: var(--bg-card);
|
|
3990
|
+
color: var(--text);
|
|
3991
|
+
}
|
|
3992
|
+
|
|
3993
|
+
.home-announcements-dots {
|
|
3994
|
+
display: flex;
|
|
3995
|
+
justify-content: center;
|
|
3996
|
+
gap: 8px;
|
|
3997
|
+
margin-top: 16px;
|
|
3998
|
+
}
|
|
3999
|
+
|
|
4000
|
+
.home-announcement-dot {
|
|
4001
|
+
width: 8px;
|
|
4002
|
+
height: 8px;
|
|
4003
|
+
border-radius: 50%;
|
|
4004
|
+
background: var(--border);
|
|
4005
|
+
cursor: pointer;
|
|
4006
|
+
transition: all 0.2s;
|
|
4007
|
+
}
|
|
4008
|
+
|
|
4009
|
+
.home-announcement-dot.active {
|
|
4010
|
+
background: linear-gradient(135deg, #00ED64, #00C2FF);
|
|
4011
|
+
}
|
|
4012
|
+
|
|
4013
|
+
/* Section Headers */
|
|
4014
|
+
.home-section {
|
|
4015
|
+
margin-bottom: 48px;
|
|
4016
|
+
}
|
|
4017
|
+
|
|
4018
|
+
.home-section-header {
|
|
4019
|
+
display: flex;
|
|
4020
|
+
align-items: center;
|
|
4021
|
+
justify-content: space-between;
|
|
4022
|
+
margin-bottom: 24px;
|
|
4023
|
+
}
|
|
4024
|
+
|
|
4025
|
+
.home-section-header h3 {
|
|
4026
|
+
font-size: 20px;
|
|
4027
|
+
font-weight: 700;
|
|
4028
|
+
color: var(--accent-text);
|
|
4029
|
+
}
|
|
4030
|
+
|
|
4031
|
+
.home-section-header a,
|
|
4032
|
+
.home-marketplace-cta {
|
|
4033
|
+
color: var(--blue);
|
|
4034
|
+
text-decoration: none;
|
|
4035
|
+
font-weight: 500;
|
|
4036
|
+
transition: all 0.2s;
|
|
4037
|
+
}
|
|
4038
|
+
|
|
4039
|
+
.home-marketplace-cta {
|
|
4040
|
+
background: linear-gradient(135deg, #00ED64, #00C2FF);
|
|
4041
|
+
color: white;
|
|
4042
|
+
border: none;
|
|
4043
|
+
padding: 10px 20px;
|
|
4044
|
+
border-radius: 8px;
|
|
4045
|
+
cursor: pointer;
|
|
4046
|
+
font-weight: 600;
|
|
4047
|
+
transition: all 0.2s;
|
|
4048
|
+
}
|
|
4049
|
+
|
|
4050
|
+
.home-marketplace-cta:hover {
|
|
4051
|
+
transform: translateY(-1px);
|
|
4052
|
+
box-shadow: 0 4px 16px rgba(0, 237, 100, 0.3);
|
|
4053
|
+
}
|
|
4054
|
+
|
|
4055
|
+
.home-section-header a:hover {
|
|
4056
|
+
color: var(--accent);
|
|
4057
|
+
}
|
|
4058
|
+
|
|
4059
|
+
/* Release Notes Timeline */
|
|
4060
|
+
.home-releases-timeline {
|
|
4061
|
+
display: flex;
|
|
4062
|
+
flex-direction: column;
|
|
4063
|
+
gap: 24px;
|
|
4064
|
+
}
|
|
4065
|
+
|
|
4066
|
+
.home-release-item {
|
|
4067
|
+
display: flex;
|
|
4068
|
+
gap: 16px;
|
|
4069
|
+
padding: 24px;
|
|
4070
|
+
background: var(--bg-card);
|
|
4071
|
+
border: 1px solid var(--border);
|
|
4072
|
+
border-radius: 12px;
|
|
4073
|
+
border-left: 4px solid transparent;
|
|
4074
|
+
border-left-color: #00ED64;
|
|
4075
|
+
background: linear-gradient(90deg, transparent, var(--bg-card) 4px);
|
|
4076
|
+
transition: all 0.2s;
|
|
4077
|
+
animation: fadeInUp 0.6s ease-out;
|
|
4078
|
+
}
|
|
4079
|
+
|
|
4080
|
+
.home-release-item:hover {
|
|
4081
|
+
background: var(--bg-surface);
|
|
4082
|
+
border-color: var(--accent);
|
|
4083
|
+
}
|
|
4084
|
+
|
|
4085
|
+
.home-release-version {
|
|
4086
|
+
font-size: 16px;
|
|
4087
|
+
font-weight: 700;
|
|
4088
|
+
color: var(--accent-text);
|
|
4089
|
+
margin-bottom: 4px;
|
|
4090
|
+
}
|
|
4091
|
+
|
|
4092
|
+
.home-release-date {
|
|
4093
|
+
font-size: 12px;
|
|
4094
|
+
color: var(--text-muted);
|
|
4095
|
+
margin-bottom: 12px;
|
|
4096
|
+
}
|
|
4097
|
+
|
|
4098
|
+
.home-release-highlights {
|
|
4099
|
+
list-style: none;
|
|
4100
|
+
display: flex;
|
|
4101
|
+
flex-direction: column;
|
|
4102
|
+
gap: 8px;
|
|
4103
|
+
}
|
|
4104
|
+
|
|
4105
|
+
.home-release-highlights li {
|
|
4106
|
+
display: flex;
|
|
4107
|
+
align-items: flex-start;
|
|
4108
|
+
gap: 8px;
|
|
4109
|
+
color: var(--text-dim);
|
|
4110
|
+
line-height: 1.5;
|
|
4111
|
+
}
|
|
4112
|
+
|
|
4113
|
+
.home-release-highlights li:before {
|
|
4114
|
+
content: "•";
|
|
4115
|
+
color: var(--accent);
|
|
4116
|
+
font-weight: bold;
|
|
4117
|
+
flex-shrink: 0;
|
|
4118
|
+
margin-top: 2px;
|
|
4119
|
+
}
|
|
4120
|
+
|
|
4121
|
+
/* Subsections */
|
|
4122
|
+
.home-subsection {
|
|
4123
|
+
margin-bottom: 32px;
|
|
4124
|
+
}
|
|
4125
|
+
|
|
4126
|
+
.home-subsection h4 {
|
|
4127
|
+
font-size: 16px;
|
|
4128
|
+
font-weight: 600;
|
|
4129
|
+
color: var(--text);
|
|
4130
|
+
margin-bottom: 16px;
|
|
4131
|
+
}
|
|
4132
|
+
|
|
4133
|
+
.home-subsection-header {
|
|
4134
|
+
display: flex;
|
|
4135
|
+
align-items: center;
|
|
4136
|
+
justify-content: space-between;
|
|
4137
|
+
margin-bottom: 16px;
|
|
4138
|
+
}
|
|
4139
|
+
|
|
4140
|
+
/* Workflow Carousels */
|
|
4141
|
+
.home-workflows-carousel {
|
|
4142
|
+
display: flex;
|
|
4143
|
+
gap: 16px;
|
|
4144
|
+
overflow-x: auto;
|
|
4145
|
+
padding-bottom: 8px;
|
|
4146
|
+
scroll-snap-type: x mandatory;
|
|
4147
|
+
}
|
|
4148
|
+
|
|
4149
|
+
.home-workflows-carousel::-webkit-scrollbar {
|
|
4150
|
+
height: 6px;
|
|
4151
|
+
}
|
|
4152
|
+
|
|
4153
|
+
.home-workflows-carousel::-webkit-scrollbar-track {
|
|
4154
|
+
background: var(--bg-surface);
|
|
4155
|
+
border-radius: 3px;
|
|
4156
|
+
}
|
|
4157
|
+
|
|
4158
|
+
.home-workflows-carousel::-webkit-scrollbar-thumb {
|
|
4159
|
+
background: var(--border);
|
|
4160
|
+
border-radius: 3px;
|
|
4161
|
+
}
|
|
4162
|
+
|
|
4163
|
+
.home-workflow-card {
|
|
4164
|
+
flex: 0 0 280px;
|
|
4165
|
+
background: var(--bg-card);
|
|
4166
|
+
border: 1px solid var(--border);
|
|
4167
|
+
border-radius: 12px;
|
|
4168
|
+
padding: 20px;
|
|
4169
|
+
scroll-snap-align: start;
|
|
4170
|
+
transition: all 0.2s;
|
|
4171
|
+
animation: fadeInUp 0.6s ease-out;
|
|
4172
|
+
}
|
|
4173
|
+
|
|
4174
|
+
.home-workflow-card:hover {
|
|
4175
|
+
background: var(--bg-surface);
|
|
4176
|
+
border-color: var(--accent);
|
|
4177
|
+
transform: translateY(-2px);
|
|
4178
|
+
box-shadow: 0 8px 32px rgba(0, 212, 170, 0.1);
|
|
4179
|
+
}
|
|
4180
|
+
|
|
4181
|
+
.home-workflow-card h5 {
|
|
4182
|
+
font-size: 14px;
|
|
4183
|
+
font-weight: 600;
|
|
4184
|
+
color: var(--accent-text);
|
|
4185
|
+
margin-bottom: 8px;
|
|
4186
|
+
}
|
|
4187
|
+
|
|
4188
|
+
.home-workflow-card p {
|
|
4189
|
+
font-size: 12px;
|
|
4190
|
+
color: var(--text-dim);
|
|
4191
|
+
line-height: 1.5;
|
|
4192
|
+
margin-bottom: 12px;
|
|
4193
|
+
display: -webkit-box;
|
|
4194
|
+
-webkit-line-clamp: 2;
|
|
4195
|
+
-webkit-box-orient: vertical;
|
|
4196
|
+
overflow: hidden;
|
|
4197
|
+
}
|
|
4198
|
+
|
|
4199
|
+
.home-workflow-meta {
|
|
4200
|
+
display: flex;
|
|
4201
|
+
align-items: center;
|
|
4202
|
+
gap: 8px;
|
|
4203
|
+
margin-bottom: 16px;
|
|
4204
|
+
}
|
|
4205
|
+
|
|
4206
|
+
.home-workflow-domain {
|
|
4207
|
+
background: rgba(0, 237, 100, 0.1);
|
|
4208
|
+
color: var(--accent);
|
|
4209
|
+
padding: 4px 8px;
|
|
4210
|
+
border-radius: 6px;
|
|
4211
|
+
font-size: 10px;
|
|
4212
|
+
font-weight: 500;
|
|
4213
|
+
}
|
|
4214
|
+
|
|
4215
|
+
.home-workflow-author {
|
|
4216
|
+
font-size: 11px;
|
|
4217
|
+
color: var(--text-muted);
|
|
4218
|
+
}
|
|
4219
|
+
|
|
4220
|
+
.home-workflow-actions {
|
|
4221
|
+
display: flex;
|
|
4222
|
+
gap: 8px;
|
|
4223
|
+
}
|
|
4224
|
+
|
|
4225
|
+
.home-workflow-btn {
|
|
4226
|
+
flex: 1;
|
|
4227
|
+
background: none;
|
|
4228
|
+
border: 1px solid var(--border);
|
|
4229
|
+
color: var(--text-dim);
|
|
4230
|
+
padding: 8px 12px;
|
|
4231
|
+
border-radius: 6px;
|
|
4232
|
+
font-size: 11px;
|
|
4233
|
+
cursor: pointer;
|
|
4234
|
+
transition: all 0.2s;
|
|
4235
|
+
}
|
|
4236
|
+
|
|
4237
|
+
.home-workflow-btn:hover {
|
|
4238
|
+
background: var(--accent);
|
|
4239
|
+
color: white;
|
|
4240
|
+
border-color: var(--accent);
|
|
4241
|
+
}
|
|
4242
|
+
|
|
4243
|
+
/* Domain Pills */
|
|
4244
|
+
.home-domain-pills {
|
|
4245
|
+
display: flex;
|
|
4246
|
+
flex-wrap: wrap;
|
|
4247
|
+
gap: 8px;
|
|
4248
|
+
}
|
|
4249
|
+
|
|
4250
|
+
.home-domain-pill {
|
|
4251
|
+
background: var(--bg-card);
|
|
4252
|
+
border: 1px solid var(--border);
|
|
4253
|
+
color: var(--text-dim);
|
|
4254
|
+
padding: 8px 16px;
|
|
4255
|
+
border-radius: 20px;
|
|
4256
|
+
font-size: 12px;
|
|
4257
|
+
font-weight: 500;
|
|
4258
|
+
cursor: pointer;
|
|
4259
|
+
transition: all 0.2s;
|
|
4260
|
+
}
|
|
4261
|
+
|
|
4262
|
+
.home-domain-pill:hover {
|
|
4263
|
+
background: linear-gradient(135deg, #00ED64, #00C2FF);
|
|
4264
|
+
color: white;
|
|
4265
|
+
border-color: transparent;
|
|
4266
|
+
transform: translateY(-1px);
|
|
4267
|
+
}
|
|
4268
|
+
|
|
4269
|
+
/* Sort Tabs */
|
|
4270
|
+
.home-sort-tabs {
|
|
4271
|
+
display: flex;
|
|
4272
|
+
gap: 2px;
|
|
4273
|
+
background: var(--bg-surface);
|
|
4274
|
+
border: 1px solid var(--border);
|
|
4275
|
+
border-radius: 8px;
|
|
4276
|
+
padding: 4px;
|
|
4277
|
+
}
|
|
4278
|
+
|
|
4279
|
+
.home-sort-tab {
|
|
4280
|
+
background: none;
|
|
4281
|
+
border: none;
|
|
4282
|
+
color: var(--text-muted);
|
|
4283
|
+
padding: 6px 12px;
|
|
4284
|
+
border-radius: 6px;
|
|
4285
|
+
font-size: 12px;
|
|
4286
|
+
cursor: pointer;
|
|
4287
|
+
transition: all 0.2s;
|
|
4288
|
+
}
|
|
4289
|
+
|
|
4290
|
+
.home-sort-tab.active {
|
|
4291
|
+
background: var(--accent);
|
|
4292
|
+
color: white;
|
|
4293
|
+
}
|
|
4294
|
+
|
|
4295
|
+
.home-sort-tab:hover:not(.active) {
|
|
4296
|
+
background: var(--bg-card);
|
|
4297
|
+
color: var(--text);
|
|
4298
|
+
}
|
|
4299
|
+
|
|
4300
|
+
/* Community Grid */
|
|
4301
|
+
.home-community-grid {
|
|
4302
|
+
display: grid;
|
|
4303
|
+
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
4304
|
+
gap: 16px;
|
|
4305
|
+
}
|
|
4306
|
+
|
|
4307
|
+
.home-community-empty {
|
|
4308
|
+
grid-column: 1 / -1;
|
|
4309
|
+
text-align: center;
|
|
4310
|
+
padding: 48px;
|
|
4311
|
+
color: var(--text-muted);
|
|
4312
|
+
}
|
|
4313
|
+
|
|
4314
|
+
.home-community-empty h4 {
|
|
4315
|
+
font-size: 16px;
|
|
4316
|
+
margin-bottom: 8px;
|
|
4317
|
+
color: var(--text);
|
|
4318
|
+
}
|
|
4319
|
+
|
|
4320
|
+
/* Quick Actions */
|
|
4321
|
+
.home-quick-actions {
|
|
4322
|
+
display: grid;
|
|
4323
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
4324
|
+
gap: 16px;
|
|
4325
|
+
}
|
|
4326
|
+
|
|
4327
|
+
.home-quick-action {
|
|
4328
|
+
display: flex;
|
|
4329
|
+
flex-direction: column;
|
|
4330
|
+
align-items: center;
|
|
4331
|
+
gap: 12px;
|
|
4332
|
+
background: var(--bg-card);
|
|
4333
|
+
border: 1px solid var(--border);
|
|
4334
|
+
border-radius: 12px;
|
|
4335
|
+
padding: 24px;
|
|
4336
|
+
cursor: pointer;
|
|
4337
|
+
transition: all 0.2s;
|
|
4338
|
+
text-decoration: none;
|
|
4339
|
+
color: var(--text);
|
|
4340
|
+
animation: fadeInUp 0.6s ease-out;
|
|
4341
|
+
}
|
|
4342
|
+
|
|
4343
|
+
.home-quick-action:hover {
|
|
4344
|
+
background: var(--bg-surface);
|
|
4345
|
+
border-color: var(--accent);
|
|
4346
|
+
transform: translateY(-4px);
|
|
4347
|
+
box-shadow: 0 12px 40px rgba(0, 212, 170, 0.1);
|
|
4348
|
+
color: var(--text);
|
|
4349
|
+
}
|
|
4350
|
+
|
|
4351
|
+
.home-quick-action svg {
|
|
4352
|
+
width: 24px;
|
|
4353
|
+
height: 24px;
|
|
4354
|
+
color: var(--accent);
|
|
4355
|
+
}
|
|
4356
|
+
|
|
4357
|
+
.home-quick-action span {
|
|
4358
|
+
font-size: 14px;
|
|
4359
|
+
font-weight: 500;
|
|
4360
|
+
text-align: center;
|
|
4361
|
+
}
|
|
4362
|
+
|
|
4363
|
+
/* Footer */
|
|
4364
|
+
.home-footer {
|
|
4365
|
+
margin-top: 64px;
|
|
4366
|
+
padding-top: 32px;
|
|
4367
|
+
border-top: 1px solid var(--border);
|
|
4368
|
+
text-align: center;
|
|
4369
|
+
}
|
|
4370
|
+
|
|
4371
|
+
.home-footer-links {
|
|
4372
|
+
display: flex;
|
|
4373
|
+
justify-content: center;
|
|
4374
|
+
gap: 24px;
|
|
4375
|
+
margin-bottom: 16px;
|
|
4376
|
+
flex-wrap: wrap;
|
|
4377
|
+
}
|
|
4378
|
+
|
|
4379
|
+
.home-footer-links a {
|
|
4380
|
+
color: var(--text-dim);
|
|
4381
|
+
text-decoration: none;
|
|
4382
|
+
font-size: 12px;
|
|
4383
|
+
transition: color 0.2s;
|
|
4384
|
+
}
|
|
4385
|
+
|
|
4386
|
+
.home-footer-links a:hover {
|
|
4387
|
+
color: var(--accent);
|
|
4388
|
+
}
|
|
4389
|
+
|
|
4390
|
+
.home-footer-branding {
|
|
4391
|
+
display: flex;
|
|
4392
|
+
flex-direction: column;
|
|
4393
|
+
gap: 4px;
|
|
4394
|
+
color: var(--text-muted);
|
|
4395
|
+
font-size: 11px;
|
|
4396
|
+
}
|
|
4397
|
+
|
|
4398
|
+
/* Animations */
|
|
4399
|
+
@keyframes fadeIn {
|
|
4400
|
+
from { opacity: 0; }
|
|
4401
|
+
to { opacity: 1; }
|
|
4402
|
+
}
|
|
4403
|
+
|
|
4404
|
+
@keyframes fadeInUp {
|
|
4405
|
+
from {
|
|
4406
|
+
opacity: 0;
|
|
4407
|
+
transform: translateY(20px);
|
|
4408
|
+
}
|
|
4409
|
+
to {
|
|
4410
|
+
opacity: 1;
|
|
4411
|
+
transform: translateY(0);
|
|
4412
|
+
}
|
|
4413
|
+
}
|
|
4414
|
+
|
|
4415
|
+
@keyframes slideDown {
|
|
4416
|
+
from {
|
|
4417
|
+
opacity: 0;
|
|
4418
|
+
transform: translateY(-10px);
|
|
4419
|
+
}
|
|
4420
|
+
to {
|
|
4421
|
+
opacity: 1;
|
|
4422
|
+
transform: translateY(0);
|
|
4423
|
+
}
|
|
4424
|
+
}
|
|
4425
|
+
|
|
4426
|
+
/* Responsive */
|
|
4427
|
+
@media (max-width: 1024px) {
|
|
4428
|
+
.home-content {
|
|
4429
|
+
padding: 24px;
|
|
4430
|
+
}
|
|
4431
|
+
|
|
4432
|
+
.home-quick-actions {
|
|
4433
|
+
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
|
4434
|
+
}
|
|
4435
|
+
|
|
4436
|
+
.home-community-grid {
|
|
4437
|
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
4438
|
+
}
|
|
4439
|
+
}
|
|
4440
|
+
|
|
4441
|
+
@media (max-width: 768px) {
|
|
4442
|
+
.home-header {
|
|
4443
|
+
padding: 16px 24px 12px;
|
|
4444
|
+
}
|
|
4445
|
+
|
|
4446
|
+
.home-content {
|
|
4447
|
+
padding: 16px;
|
|
4448
|
+
}
|
|
4449
|
+
|
|
4450
|
+
.home-section-header {
|
|
4451
|
+
flex-direction: column;
|
|
4452
|
+
align-items: flex-start;
|
|
4453
|
+
gap: 12px;
|
|
4454
|
+
}
|
|
4455
|
+
|
|
4456
|
+
.home-footer-links {
|
|
4457
|
+
flex-direction: column;
|
|
4458
|
+
gap: 12px;
|
|
4459
|
+
}
|
|
4460
|
+
}
|
|
4461
|
+
</style>
|
|
4462
|
+
</head>
|
|
4463
|
+
<body>
|
|
4464
|
+
|
|
4465
|
+
<!-- Lucide Icons (lucide.dev) — stroke-based, 16×16 -->
|
|
4466
|
+
<svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
|
|
4467
|
+
<!-- Zap (Embed) -->
|
|
4468
|
+
<symbol id="lg-lightning" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4469
|
+
<path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"/>
|
|
4470
|
+
</symbol>
|
|
4471
|
+
<!-- Arrow Left Right (Compare) -->
|
|
4472
|
+
<symbol id="lg-arrows" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4473
|
+
<path d="M8 3 4 7l4 4"/><path d="M4 7h16"/><path d="m16 21 4-4-4-4"/><path d="M20 17H4"/>
|
|
4474
|
+
</symbol>
|
|
4475
|
+
<!-- Search -->
|
|
4476
|
+
<symbol id="lg-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4477
|
+
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>
|
|
4478
|
+
</symbol>
|
|
4479
|
+
<!-- Gauge (Benchmark) -->
|
|
4480
|
+
<symbol id="lg-gauge" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4481
|
+
<path d="m12 14 4-4"/><path d="M3.34 19a10 10 0 1 1 17.32 0"/>
|
|
4482
|
+
</symbol>
|
|
4483
|
+
<!-- Lightbulb (Explore) -->
|
|
4484
|
+
<symbol id="lg-bulb" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4485
|
+
<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"/>
|
|
4486
|
+
</symbol>
|
|
4487
|
+
<!-- Info (About) -->
|
|
4488
|
+
<symbol id="lg-info" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4489
|
+
<circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/>
|
|
4490
|
+
</symbol>
|
|
4491
|
+
<!-- Image (Multimodal) -->
|
|
4492
|
+
<symbol id="lg-image" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4493
|
+
<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"/>
|
|
4494
|
+
</symbol>
|
|
4495
|
+
<!-- Settings (Config) -->
|
|
4496
|
+
<symbol id="lg-config" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4497
|
+
<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"/>
|
|
4498
|
+
</symbol>
|
|
4499
|
+
<!-- Code (Generate) -->
|
|
4500
|
+
<symbol id="lg-code" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4501
|
+
<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>
|
|
4502
|
+
</symbol>
|
|
4503
|
+
<!-- Palette (Theme) -->
|
|
4504
|
+
<symbol id="lg-palette" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4505
|
+
<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"/>
|
|
4506
|
+
</symbol>
|
|
4507
|
+
<!-- Box (Cube) -->
|
|
4508
|
+
<symbol id="lg-cube" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4509
|
+
<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"/>
|
|
4510
|
+
</symbol>
|
|
4511
|
+
<!-- Message Square (Chat) -->
|
|
4512
|
+
<symbol id="lg-chat" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4513
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
|
4514
|
+
</symbol>
|
|
4515
|
+
<!-- Shield -->
|
|
4516
|
+
<symbol id="lg-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4517
|
+
<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"/>
|
|
4518
|
+
</symbol>
|
|
4519
|
+
<!-- Activity (Pulse) -->
|
|
4520
|
+
<symbol id="lg-pulse" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
4521
|
+
<path d="M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36-3.18-19.64A2 2 0 0 0 10.12 1h-.24a2 2 0 0 0-1.94 1.55L5.18 12H2"/>
|
|
4522
|
+
</symbol>
|
|
4523
|
+
</svg>
|
|
4524
|
+
|
|
4525
|
+
<div class="app-shell">
|
|
4526
|
+
|
|
4527
|
+
<!-- Sidebar -->
|
|
4528
|
+
<aside class="sidebar">
|
|
4529
|
+
<div class="sidebar-drag-region">
|
|
4530
|
+
<img class="sidebar-logo" id="sidebarLogo" src="/icons/dark/64.png" alt="Vai">
|
|
4531
|
+
<span class="sidebar-title">Vai</span>
|
|
4532
|
+
<button class="sidebar-settings-btn" data-tab="settings" title="Settings"><svg width="16" height="16" viewBox="0 0 24 24"><use href="#lg-config"/></svg></button>
|
|
4533
|
+
</div>
|
|
4534
|
+
<nav class="sidebar-nav">
|
|
4535
|
+
<div class="sidebar-nav-group" role="tablist" aria-label="Tools">
|
|
4536
|
+
<div class="sidebar-nav-label" id="nav-tools-label">Tools</div>
|
|
4537
|
+
<button class="tab-btn active" data-tab="home" role="tab" aria-selected="true" aria-controls="tab-home" id="tab-btn-home"><span class="tab-btn-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"/><path d="M3 10a2 2 0 0 1 .709-1.528l7-5.999a2 2 0 0 1 2.582 0l7 5.999A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg></span><span>Home</span></button>
|
|
4538
|
+
<button class="tab-btn" data-tab="embed" role="tab" aria-selected="false" aria-controls="tab-embed" id="tab-btn-embed"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-lightning"/></svg></span><span>Embed</span></button>
|
|
4539
|
+
<button class="tab-btn" data-tab="compare" role="tab" aria-selected="false" aria-controls="tab-compare" id="tab-btn-compare"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-arrows"/></svg></span><span>Compare</span></button>
|
|
4540
|
+
<button class="tab-btn" data-tab="search" role="tab" aria-selected="false" aria-controls="tab-search" id="tab-btn-search"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-search"/></svg></span><span>Search</span></button>
|
|
4541
|
+
<button class="tab-btn" data-tab="multimodal" role="tab" aria-selected="false" aria-controls="tab-multimodal" id="tab-btn-multimodal"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-image"/></svg></span><span>Multimodal</span></button>
|
|
4542
|
+
<button class="tab-btn" data-tab="generate" role="tab" aria-selected="false" aria-controls="tab-generate" id="tab-btn-generate"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-code"/></svg></span><span>Generate</span></button>
|
|
4543
|
+
<button class="tab-btn" data-tab="chat" role="tab" aria-selected="false" aria-controls="tab-chat" id="tab-btn-chat"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-chat"/></svg></span><span>Chat</span></button>
|
|
4544
|
+
<button class="tab-btn" data-tab="workflows" role="tab" aria-selected="false" aria-controls="tab-workflows" id="tab-btn-workflows"><span class="tab-btn-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="5" cy="12" r="3"/><circle cx="19" cy="6" r="3"/><circle cx="19" cy="18" r="3"/><line x1="7.7" y1="10.7" x2="16.3" y2="7.3"/><line x1="7.7" y1="13.3" x2="16.3" y2="16.7"/></svg></span><span>Workflows</span></button>
|
|
4545
|
+
</div>
|
|
4546
|
+
<div class="sidebar-nav-divider"></div>
|
|
4547
|
+
<div class="sidebar-nav-group" role="tablist" aria-label="Learn">
|
|
4548
|
+
<div class="sidebar-nav-label" id="nav-learn-label">Learn</div>
|
|
4549
|
+
<button class="tab-btn" data-tab="benchmark" role="tab" aria-selected="false" aria-controls="tab-benchmark" id="tab-btn-benchmark"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-gauge"/></svg></span><span>Benchmark</span></button>
|
|
4550
|
+
<button class="tab-btn" data-tab="explore" role="tab" aria-selected="false" aria-controls="tab-explore" id="tab-btn-explore"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-bulb"/></svg></span><span>Explore</span></button>
|
|
4551
|
+
<button class="tab-btn" data-tab="about" role="tab" aria-selected="false" aria-controls="tab-about" id="tab-btn-about"><span class="tab-btn-icon" aria-hidden="true"><svg><use href="#lg-info"/></svg></span><span>About</span></button>
|
|
4552
|
+
</div>
|
|
4553
|
+
</nav>
|
|
4554
|
+
<div class="sidebar-footer">
|
|
4555
|
+
<div style="display:flex;align-items:center;gap:8px;justify-content:space-between;">
|
|
4556
|
+
<div style="display:flex;align-items:center;gap:5px;">
|
|
4557
|
+
<div class="status-dot" id="statusDot"></div>
|
|
4558
|
+
<span class="status-label" id="statusLabel">Checking...</span>
|
|
4559
|
+
</div>
|
|
4560
|
+
<button class="theme-toggle" id="themeToggle" title="Toggle light/dark mode">🌙</button>
|
|
4561
|
+
</div>
|
|
4562
|
+
<div style="display:flex;align-items:center;justify-content:space-between;">
|
|
4563
|
+
<div id="appVersionLabel" style="font-size:10px;color:var(--text-muted);"></div>
|
|
4564
|
+
<div style="display:flex;align-items:center;gap:6px;">
|
|
4565
|
+
<a class="sidebar-docs-link" href="https://docs.vaicli.com" target="_blank" rel="noopener" title="Documentation (F1)">
|
|
4566
|
+
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 1h7l3 3v11H3z"/><path d="M10 1v3h3"/><path d="M6 8h4M6 10.5h4"/></svg>
|
|
4567
|
+
<span>Docs</span>
|
|
4568
|
+
</a>
|
|
4569
|
+
<button class="sidebar-bug-link" id="bugButton" title="Report a Bug">
|
|
4570
|
+
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="9" r="4.5"/><path d="M5.5 5.5L3 3M10.5 5.5L13 3M3 9H1M15 9h-2M5.5 12.5L4 15M10.5 12.5L12 15"/></svg>
|
|
4571
|
+
<span class="sidebar-bug-label">Bug</span>
|
|
4572
|
+
</button>
|
|
4573
|
+
</div>
|
|
4574
|
+
</div>
|
|
4575
|
+
</div>
|
|
4576
|
+
</aside>
|
|
4577
|
+
|
|
4578
|
+
<!-- Content -->
|
|
4579
|
+
<div class="content-area">
|
|
4580
|
+
<div class="content-drag-region"></div>
|
|
4581
|
+
<div class="update-banner" id="updateBanner">
|
|
4582
|
+
<span class="update-banner-icon">🚀</span>
|
|
4583
|
+
<span class="update-banner-text" id="updateBannerText">
|
|
4584
|
+
<strong>Vai <span id="updateVersion"></span></strong> is available.
|
|
4585
|
+
You're on <span id="currentVersion"></span>.
|
|
4586
|
+
</span>
|
|
4587
|
+
<div class="update-progress" id="updateProgress" style="display:none;">
|
|
4588
|
+
<div class="update-progress-bar"><div class="update-progress-fill" id="updateProgressFill"></div></div>
|
|
4589
|
+
<span class="update-progress-label" id="updateProgressLabel">0%</span>
|
|
4590
|
+
</div>
|
|
4591
|
+
<button class="update-banner-btn" id="updateDownloadBtn">Download & Install</button>
|
|
4592
|
+
<button class="update-banner-btn" id="updateInstallBtn" style="display:none;">Restart & Update</button>
|
|
4593
|
+
<button class="update-banner-dismiss" id="updateDismissBtn" title="Dismiss">×</button>
|
|
4594
|
+
</div>
|
|
4595
|
+
<div class="main">
|
|
4596
|
+
|
|
4597
|
+
<!-- ========== HOME TAB ========== -->
|
|
4598
|
+
<div class="tab-panel active" id="tab-home" role="tabpanel" aria-labelledby="tab-btn-home" tabindex="0">
|
|
4599
|
+
<!-- Header Bar -->
|
|
4600
|
+
<div class="home-header">
|
|
4601
|
+
<div class="home-header-left">
|
|
4602
|
+
<img class="home-logo" id="homeLogo" src="/icons/V.png" alt="Vai" onerror="this.src='/icons/dark/64.png'">
|
|
4603
|
+
<span class="home-header-title">Voyage AI Playground</span>
|
|
4604
|
+
<span class="home-version-badge" id="homeVersionBadge">v1.0.0</span>
|
|
4605
|
+
</div>
|
|
4606
|
+
<div class="home-header-right">
|
|
4607
|
+
<button class="home-settings-btn" onclick="switchTab('settings')" title="Settings">
|
|
4608
|
+
<svg width="20" height="20" viewBox="0 0 24 24"><use href="#lg-config"/></svg>
|
|
4609
|
+
</button>
|
|
4610
|
+
</div>
|
|
4611
|
+
</div>
|
|
4612
|
+
|
|
4613
|
+
<!-- API Key Warning Banner (hidden by default) -->
|
|
4614
|
+
<div class="home-api-warning" id="homeApiWarning" style="display: none;">
|
|
4615
|
+
<div class="home-api-warning-content">
|
|
4616
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">
|
|
4617
|
+
<triangle cx="12" cy="12" r="10"/>
|
|
4618
|
+
<line x1="12" y1="8" x2="12" y2="12"/>
|
|
4619
|
+
<line x1="12" y1="16" x2="12.01" y2="16"/>
|
|
4620
|
+
</svg>
|
|
4621
|
+
<span>Set up your Voyage AI API key to get started</span>
|
|
4622
|
+
<button onclick="switchTab('settings')">Settings →</button>
|
|
4623
|
+
</div>
|
|
4624
|
+
</div>
|
|
4625
|
+
|
|
4626
|
+
<div class="home-content">
|
|
4627
|
+
<!-- Announcements Banner -->
|
|
4628
|
+
<div class="home-announcements" id="homeAnnouncements" style="display: none;">
|
|
4629
|
+
<div class="home-announcements-carousel" id="announcementsCarousel">
|
|
4630
|
+
<!-- Cards will be inserted here -->
|
|
4631
|
+
</div>
|
|
4632
|
+
<div class="home-announcements-dots" id="announcementsDots">
|
|
4633
|
+
<!-- Dots will be inserted here -->
|
|
4634
|
+
</div>
|
|
4635
|
+
</div>
|
|
4636
|
+
|
|
4637
|
+
<!-- What's New (Release Notes) -->
|
|
4638
|
+
<div class="home-section" id="homeReleases">
|
|
4639
|
+
<div class="home-section-header">
|
|
4640
|
+
<h3>What's New</h3>
|
|
4641
|
+
<a href="https://github.com/mrlynn/voyageai-cli/releases" target="_blank" rel="noopener">View All →</a>
|
|
4642
|
+
</div>
|
|
4643
|
+
<div class="home-releases-timeline" id="releasesTimeline">
|
|
4644
|
+
<!-- Release items will be inserted here -->
|
|
4645
|
+
</div>
|
|
4646
|
+
</div>
|
|
4647
|
+
|
|
4648
|
+
<!-- Marketplace Spotlight -->
|
|
4649
|
+
<div class="home-section">
|
|
4650
|
+
<div class="home-section-header">
|
|
4651
|
+
<h3>Marketplace Spotlight</h3>
|
|
4652
|
+
<button class="home-marketplace-cta" onclick="switchTab('workflows')">
|
|
4653
|
+
Explore the Marketplace →
|
|
4654
|
+
</button>
|
|
4655
|
+
</div>
|
|
4656
|
+
|
|
4657
|
+
<!-- Featured Workflows -->
|
|
4658
|
+
<div class="home-subsection">
|
|
4659
|
+
<h4>Featured Workflows</h4>
|
|
4660
|
+
<div class="home-workflows-carousel" id="featuredWorkflows">
|
|
4661
|
+
<!-- Workflow cards will be inserted here -->
|
|
4662
|
+
</div>
|
|
4663
|
+
</div>
|
|
4664
|
+
|
|
4665
|
+
<!-- Browse by Domain -->
|
|
4666
|
+
<div class="home-subsection">
|
|
4667
|
+
<h4>Browse by Domain</h4>
|
|
4668
|
+
<div class="home-domain-pills">
|
|
4669
|
+
<button class="home-domain-pill" onclick="filterAndSwitchWorkflows('Healthcare')">Healthcare</button>
|
|
4670
|
+
<button class="home-domain-pill" onclick="filterAndSwitchWorkflows('Finance')">Finance</button>
|
|
4671
|
+
<button class="home-domain-pill" onclick="filterAndSwitchWorkflows('Legal')">Legal</button>
|
|
4672
|
+
<button class="home-domain-pill" onclick="filterAndSwitchWorkflows('DevOps')">DevOps</button>
|
|
4673
|
+
<button class="home-domain-pill" onclick="filterAndSwitchWorkflows('Education')">Education</button>
|
|
4674
|
+
<button class="home-domain-pill" onclick="filterAndSwitchWorkflows('E-Commerce')">E-Commerce</button>
|
|
4675
|
+
<button class="home-domain-pill" onclick="filterAndSwitchWorkflows('Marketing')">Marketing</button>
|
|
4676
|
+
</div>
|
|
4677
|
+
</div>
|
|
4678
|
+
|
|
4679
|
+
<!-- Community Workflows -->
|
|
4680
|
+
<div class="home-subsection">
|
|
4681
|
+
<div class="home-subsection-header">
|
|
4682
|
+
<h4>Community Workflows</h4>
|
|
4683
|
+
<div class="home-sort-tabs">
|
|
4684
|
+
<button class="home-sort-tab active" data-sort="trending" onclick="sortCommunityWorkflows('trending')">Trending</button>
|
|
4685
|
+
<button class="home-sort-tab" data-sort="newest" onclick="sortCommunityWorkflows('newest')">Newest</button>
|
|
4686
|
+
<button class="home-sort-tab" data-sort="installs" onclick="sortCommunityWorkflows('installs')">Most Installed</button>
|
|
4687
|
+
</div>
|
|
4688
|
+
</div>
|
|
4689
|
+
<div class="home-community-grid" id="communityWorkflows">
|
|
4690
|
+
<!-- Community workflow cards will be inserted here -->
|
|
4691
|
+
</div>
|
|
4692
|
+
</div>
|
|
4693
|
+
</div>
|
|
4694
|
+
|
|
4695
|
+
<!-- Quick Actions -->
|
|
4696
|
+
<div class="home-section">
|
|
4697
|
+
<div class="home-section-header">
|
|
4698
|
+
<h3>Quick Actions</h3>
|
|
4699
|
+
</div>
|
|
4700
|
+
<div class="home-quick-actions">
|
|
4701
|
+
<button class="home-quick-action" onclick="switchTab('embed')" title="Generate embeddings for text">
|
|
4702
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">
|
|
4703
|
+
<path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"/>
|
|
4704
|
+
</svg>
|
|
4705
|
+
<span>New Embedding</span>
|
|
4706
|
+
</button>
|
|
4707
|
+
<button class="home-quick-action" onclick="switchTab('compare')" title="Compare model similarities">
|
|
4708
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">
|
|
4709
|
+
<path d="M21 12c0 1.2-4.03 6-9 6s-9-4.8-9-6c0-1.2 4.03-6 9-6s9 4.8 9 6"/>
|
|
4710
|
+
<circle cx="12" cy="12" r="3"/>
|
|
4711
|
+
</svg>
|
|
4712
|
+
<span>Compare Models</span>
|
|
4713
|
+
</button>
|
|
4714
|
+
<button class="home-quick-action" onclick="switchTab('search')" title="Search through vectors">
|
|
4715
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">
|
|
4716
|
+
<circle cx="11" cy="11" r="8"/>
|
|
4717
|
+
<path d="m21 21-4.35-4.35"/>
|
|
4718
|
+
</svg>
|
|
4719
|
+
<span>Search Vectors</span>
|
|
4720
|
+
</button>
|
|
4721
|
+
<button class="home-quick-action" onclick="switchTab('workflows')" title="View installed workflows">
|
|
4722
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">
|
|
4723
|
+
<circle cx="5" cy="12" r="3"/>
|
|
4724
|
+
<circle cx="19" cy="6" r="3"/>
|
|
4725
|
+
<circle cx="19" cy="18" r="3"/>
|
|
4726
|
+
<line x1="7.7" y1="10.7" x2="16.3" y2="7.3"/>
|
|
4727
|
+
<line x1="7.7" y1="13.3" x2="16.3" y2="16.7"/>
|
|
4728
|
+
</svg>
|
|
4729
|
+
<span>My Workflows</span>
|
|
4730
|
+
</button>
|
|
4731
|
+
<button class="home-quick-action" onclick="publishWorkflow()" title="Publish a new workflow">
|
|
4732
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">
|
|
4733
|
+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
|
|
4734
|
+
<polyline points="17,8 12,3 7,8"/>
|
|
4735
|
+
<line x1="12" y1="3" x2="12" y2="15"/>
|
|
4736
|
+
</svg>
|
|
4737
|
+
<span>Publish Workflow</span>
|
|
4738
|
+
</button>
|
|
4739
|
+
</div>
|
|
4740
|
+
</div>
|
|
4741
|
+
|
|
4742
|
+
<!-- Footer -->
|
|
4743
|
+
<div class="home-footer">
|
|
4744
|
+
<div class="home-footer-links">
|
|
4745
|
+
<a href="https://docs.vaicli.com" target="_blank" rel="noopener">Documentation</a>
|
|
4746
|
+
<a href="https://github.com/mrlynn/voyageai-cli" target="_blank" rel="noopener">GitHub</a>
|
|
4747
|
+
<a href="#" onclick="reportBug()">Report Bug</a>
|
|
4748
|
+
<!-- <a href="#" target="_blank" rel="noopener">Community / Discord</a> -->
|
|
4749
|
+
</div>
|
|
4750
|
+
<div class="home-footer-branding">
|
|
4751
|
+
<span>Built with Voyage AI + MongoDB Atlas</span>
|
|
4752
|
+
<span id="homeFooterVersion">Version 1.0.0 • © 2026 VAI</span>
|
|
4753
|
+
</div>
|
|
4754
|
+
</div>
|
|
4755
|
+
</div>
|
|
4756
|
+
</div>
|
|
4757
|
+
|
|
4758
|
+
<!-- ========== EMBED TAB ========== -->
|
|
4759
|
+
<div class="tab-panel" id="tab-embed" role="tabpanel" aria-labelledby="tab-btn-embed" tabindex="0">
|
|
4760
|
+
<div class="page-header">
|
|
4761
|
+
<h2 class="page-header-title">Embed</h2>
|
|
4762
|
+
<p class="page-header-subtitle">Generate vector embeddings for text</p>
|
|
4763
|
+
<p class="page-header-hint">Paste or type text below, choose a model, and hit Embed to see the raw vectors and token usage.</p>
|
|
4764
|
+
<a class="page-header-docs" href="https://docs.vaicli.com/docs/commands/embeddings/embed" target="_blank" rel="noopener" title="Embed documentation"><svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 1h7l3 3v11H3z"/><path d="M10 1v3h3"/></svg>Docs</a>
|
|
4765
|
+
</div>
|
|
4766
|
+
<div class="card">
|
|
4767
|
+
<div class="card-title">Input Text</div>
|
|
4768
|
+
<textarea id="embedInput" rows="5" placeholder="Enter text to embed..." aria-label="Text to embed">MongoDB Atlas provides powerful vector search capabilities for AI applications.</textarea>
|
|
4769
|
+
</div>
|
|
4770
|
+
|
|
4771
|
+
<div class="options-row">
|
|
4772
|
+
<div class="option-group">
|
|
4773
|
+
<span class="option-label">Model</span>
|
|
4774
|
+
<select id="embedModel"></select>
|
|
4775
|
+
</div>
|
|
4776
|
+
<div class="option-group">
|
|
4777
|
+
<span class="option-label">Input Type</span>
|
|
4778
|
+
<select id="embedInputType">
|
|
4779
|
+
<option value="">None</option>
|
|
4780
|
+
<option value="query">Query</option>
|
|
4781
|
+
<option value="document">Document</option>
|
|
4782
|
+
</select>
|
|
4783
|
+
</div>
|
|
4784
|
+
<div class="option-group">
|
|
4785
|
+
<span class="option-label">Dimensions</span>
|
|
4786
|
+
<select id="embedDimensions">
|
|
4787
|
+
<option value="">Default</option>
|
|
4788
|
+
<option value="256">256</option>
|
|
4789
|
+
<option value="512">512</option>
|
|
4790
|
+
<option value="1024">1024</option>
|
|
4791
|
+
<option value="2048">2048</option>
|
|
4792
|
+
</select>
|
|
4793
|
+
</div>
|
|
4794
|
+
<div class="option-group">
|
|
4795
|
+
<span class="option-label">Output Type</span>
|
|
4796
|
+
<select id="embedOutputDtype">
|
|
4797
|
+
<option value="float">float (32-bit)</option>
|
|
4798
|
+
<option value="int8">int8 (4× smaller)</option>
|
|
4799
|
+
<option value="uint8">uint8 (4× smaller)</option>
|
|
4800
|
+
<option value="binary">binary (32× smaller)</option>
|
|
4801
|
+
<option value="ubinary">ubinary (32× smaller)</option>
|
|
4802
|
+
</select>
|
|
4803
|
+
</div>
|
|
4804
|
+
<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>
|
|
4805
|
+
</div>
|
|
4806
|
+
|
|
4807
|
+
<div class="error-msg" id="embedError"></div>
|
|
4808
|
+
|
|
4809
|
+
<div class="result-section" id="embedResult">
|
|
4810
|
+
<div class="card">
|
|
4811
|
+
<div class="card-title">Result</div>
|
|
4812
|
+
<div id="embedStats"></div>
|
|
4813
|
+
<div class="vector-preview" id="embedVector"></div>
|
|
4814
|
+
<div style="margin-top:8px;">
|
|
4815
|
+
<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>
|
|
4816
|
+
</div>
|
|
4817
|
+
<div class="card-title" style="margin-top:16px;">Vector Heatmap</div>
|
|
4818
|
+
<div class="heatmap" id="embedHeatmap"></div>
|
|
4819
|
+
</div>
|
|
4820
|
+
</div>
|
|
4821
|
+
</div>
|
|
4822
|
+
|
|
4823
|
+
<!-- ========== COMPARE TAB ========== -->
|
|
4824
|
+
<div class="tab-panel" id="tab-compare" role="tabpanel" aria-labelledby="tab-btn-compare" tabindex="0">
|
|
4825
|
+
<div class="page-header">
|
|
4826
|
+
<h2 class="page-header-title">Compare</h2>
|
|
4827
|
+
<p class="page-header-subtitle">Visualize similarity between text pairs</p>
|
|
4828
|
+
<p class="page-header-hint">Enter two texts and compare their embeddings: see cosine similarity, a heatmap of vector dimensions, and a visual diff.</p>
|
|
4829
|
+
<a class="page-header-docs" href="https://docs.vaicli.com/docs/commands/embeddings/similarity" target="_blank" rel="noopener" title="Similarity documentation"><svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 1h7l3 3v11H3z"/><path d="M10 1v3h3"/></svg>Docs</a>
|
|
3610
4830
|
</div>
|
|
3611
4831
|
<div class="compare-grid">
|
|
3612
4832
|
<div class="card">
|
|
@@ -3634,7 +4854,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
3634
4854
|
<option value="2048">2048</option>
|
|
3635
4855
|
</select>
|
|
3636
4856
|
</div>
|
|
3637
|
-
<button class="btn" id="compareBtn" onclick="doCompare()"
|
|
4857
|
+
<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
4858
|
</div>
|
|
3639
4859
|
|
|
3640
4860
|
<div class="error-msg" id="compareError"></div>
|
|
@@ -3696,8 +4916,8 @@ Semantic search understands meaning beyond keyword matching</textarea>
|
|
|
3696
4916
|
<option value="10">10</option>
|
|
3697
4917
|
</select>
|
|
3698
4918
|
</div>
|
|
3699
|
-
<button class="btn" id="searchBtn" onclick="doSearch(false)"
|
|
3700
|
-
<button class="btn btn-secondary" id="searchRerankBtn" onclick="doSearch(true)"
|
|
4919
|
+
<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>
|
|
4920
|
+
<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
4921
|
</div>
|
|
3702
4922
|
|
|
3703
4923
|
<div class="error-msg" id="searchError"></div>
|
|
@@ -3756,7 +4976,7 @@ Semantic search understands meaning beyond keyword matching</textarea>
|
|
|
3756
4976
|
<option value="2048">2048</option>
|
|
3757
4977
|
</select>
|
|
3758
4978
|
</div>
|
|
3759
|
-
<button class="btn" id="mmCompareBtn" onclick="doMultimodalCompare()"
|
|
4979
|
+
<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
4980
|
</div>
|
|
3761
4981
|
|
|
3762
4982
|
<div class="error-msg" id="mmError"></div>
|
|
@@ -3794,20 +5014,20 @@ Semantic search understands meaning beyond keyword matching</textarea>
|
|
|
3794
5014
|
<div class="card" style="margin-top:12px;">
|
|
3795
5015
|
<div class="card-title">Search Query</div>
|
|
3796
5016
|
<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')"
|
|
5017
|
+
<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>
|
|
5018
|
+
<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
5019
|
</div>
|
|
3800
5020
|
<div id="mmSearchTextWrap">
|
|
3801
5021
|
<div class="mm-search-row">
|
|
3802
5022
|
<input type="text" id="mmSearchQuery" placeholder="Enter a search query...">
|
|
3803
|
-
<button class="btn" id="mmSearchBtn" onclick="doMultimodalSearch()"
|
|
5023
|
+
<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
5024
|
</div>
|
|
3805
5025
|
</div>
|
|
3806
5026
|
<div id="mmSearchImageWrap" style="display:none;">
|
|
3807
5027
|
<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
5028
|
<div style="display:flex;gap:8px;align-items:center;">
|
|
3809
5029
|
<span id="mmSearchImageLabel" style="font-size:13px;color:var(--text-muted);">No image selected</span>
|
|
3810
|
-
<button class="btn" id="mmSearchImgBtn" onclick="doMultimodalSearch()"
|
|
5030
|
+
<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
5031
|
</div>
|
|
3812
5032
|
</div>
|
|
3813
5033
|
</div>
|
|
@@ -3834,12 +5054,12 @@ Semantic search understands meaning beyond keyword matching</textarea>
|
|
|
3834
5054
|
|
|
3835
5055
|
<!-- Sub-panel switcher -->
|
|
3836
5056
|
<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"
|
|
5057
|
+
<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>
|
|
5058
|
+
<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>
|
|
5059
|
+
<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>
|
|
5060
|
+
<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>
|
|
5061
|
+
<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>
|
|
5062
|
+
<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
5063
|
</div>
|
|
3844
5064
|
|
|
3845
5065
|
<!-- ── Latency Panel ── -->
|
|
@@ -3861,7 +5081,7 @@ Semantic search understands meaning beyond keyword matching</textarea>
|
|
|
3861
5081
|
<option value="10">10</option>
|
|
3862
5082
|
</select>
|
|
3863
5083
|
</div>
|
|
3864
|
-
<button class="btn" id="benchLatencyBtn" onclick="doBenchLatency()"
|
|
5084
|
+
<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
5085
|
</div>
|
|
3866
5086
|
</div>
|
|
3867
5087
|
|
|
@@ -3915,7 +5135,7 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
3915
5135
|
<option value="8">8</option>
|
|
3916
5136
|
</select>
|
|
3917
5137
|
</div>
|
|
3918
|
-
<button class="btn" id="benchRankBtn" onclick="doBenchRanking()"
|
|
5138
|
+
<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
5139
|
</div>
|
|
3920
5140
|
</div>
|
|
3921
5141
|
|
|
@@ -3991,7 +5211,7 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
3991
5211
|
|
|
3992
5212
|
<!-- Cost Comparison -->
|
|
3993
5213
|
<div class="card" style="margin-top:16px;">
|
|
3994
|
-
<div class="card-title"
|
|
5214
|
+
<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
5215
|
<p style="color:var(--text-dim);font-size:13px;margin-bottom:16px;">
|
|
3996
5216
|
Voyage AI offers significant cost savings, especially with asymmetric retrieval strategies.
|
|
3997
5217
|
</p>
|
|
@@ -4152,7 +5372,7 @@ Approximate nearest neighbor algorithms like HNSW enable fast similarity search
|
|
|
4152
5372
|
Reranking models rescore initial search results to improve relevance ordering.</textarea>
|
|
4153
5373
|
</div>
|
|
4154
5374
|
<div style="margin-top:12px;">
|
|
4155
|
-
<button class="btn" id="quantBtn" onclick="doBenchQuantization()"
|
|
5375
|
+
<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
5376
|
</div>
|
|
4157
5377
|
</div>
|
|
4158
5378
|
|
|
@@ -4180,7 +5400,7 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4180
5400
|
<!-- ── Cost Panel ── -->
|
|
4181
5401
|
<div class="bench-view" id="bench-cost">
|
|
4182
5402
|
<div class="card">
|
|
4183
|
-
<div class="card-title"
|
|
5403
|
+
<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
5404
|
|
|
4185
5405
|
<!-- Mode toggle -->
|
|
4186
5406
|
<div style="margin-bottom: 20px;">
|
|
@@ -4301,7 +5521,7 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4301
5521
|
<div class="history-empty">No benchmarks recorded yet. Run a latency benchmark to start tracking.</div>
|
|
4302
5522
|
</div>
|
|
4303
5523
|
<div style="margin-top:12px;text-align:right;">
|
|
4304
|
-
<button class="btn btn-secondary btn-small" onclick="clearHistory()"
|
|
5524
|
+
<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
5525
|
</div>
|
|
4306
5526
|
</div>
|
|
4307
5527
|
</div>
|
|
@@ -4318,7 +5538,7 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4318
5538
|
<span id="chatStatusDb">No database</span>
|
|
4319
5539
|
</div>
|
|
4320
5540
|
<button class="chat-config-toggle" id="chatOpenSettings" onclick="openChatSettings()" title="Configure in Settings">
|
|
4321
|
-
<svg width="14" height="14" viewBox="0 0
|
|
5541
|
+
<svg width="14" height="14" viewBox="0 0 24 24"><use href="#lg-config"/></svg>
|
|
4322
5542
|
Configure
|
|
4323
5543
|
</button>
|
|
4324
5544
|
</div>
|
|
@@ -4342,11 +5562,18 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4342
5562
|
<div class="wf-container">
|
|
4343
5563
|
<div class="wf-library">
|
|
4344
5564
|
<div class="wf-library-header">
|
|
4345
|
-
<
|
|
5565
|
+
<div class="wf-library-tabs" style="flex:1;">
|
|
5566
|
+
<button class="wf-lib-tab active" data-lib-tab="library" onclick="wfSwitchLibTab('library')">Library</button>
|
|
5567
|
+
<button class="wf-lib-tab" data-lib-tab="palette" onclick="wfSwitchLibTab('palette')">Palette</button>
|
|
5568
|
+
</div>
|
|
5569
|
+
<button class="wf-store-btn" id="wfStoreBtn" onclick="wfStoreOpen()" title="Browse Workflow Store">
|
|
5570
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>
|
|
5571
|
+
</button>
|
|
4346
5572
|
</div>
|
|
4347
5573
|
<div class="wf-library-list" id="wfLibraryList">
|
|
4348
5574
|
<div style="padding: 16px; color: var(--text-muted); font-size: 12px;">Loading...</div>
|
|
4349
5575
|
</div>
|
|
5576
|
+
<div class="wf-palette-list" id="wfPaletteList" style="display:none; flex:1; overflow-y:auto; padding:8px;"></div>
|
|
4350
5577
|
<div class="wf-library-footer">
|
|
4351
5578
|
<button class="wf-load-file-btn" onclick="wfLoadFromFile()" title="Load workflow JSON from file">
|
|
4352
5579
|
<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 +5584,9 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4357
5584
|
</div>
|
|
4358
5585
|
<div class="wf-canvas-area">
|
|
4359
5586
|
<div class="wf-canvas-toolbar" id="wfToolbar">
|
|
5587
|
+
<button class="wf-new-btn" onclick="wfNewWorkflow()" title="Create new workflow">+ New</button>
|
|
5588
|
+
<button class="wf-edit-btn" id="wfEditBtn" onclick="wfEditWorkflow()" disabled title="Edit current workflow">✎ Edit</button>
|
|
5589
|
+
<span class="wf-toolbar-sep"></span>
|
|
4360
5590
|
<button onclick="wfZoom(1)" title="Zoom in">+</button>
|
|
4361
5591
|
<button onclick="wfZoom(-1)" title="Zoom out">−</button>
|
|
4362
5592
|
<button onclick="wfFitToView()" title="Fit to view">⊞</button>
|
|
@@ -4377,10 +5607,35 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4377
5607
|
<span class="wf-exec-status-time" id="wfExecStatusTime"></span>
|
|
4378
5608
|
</div>
|
|
4379
5609
|
<div class="wf-canvas-empty" id="wfCanvasEmpty">
|
|
4380
|
-
<div class="wf-canvas-empty-icon"
|
|
5610
|
+
<div class="wf-canvas-empty-icon"><img src="/icons/watermark.png" alt="Vai"></div>
|
|
4381
5611
|
<div class="wf-canvas-empty-text">Select a workflow from the library</div>
|
|
4382
5612
|
</div>
|
|
4383
|
-
<
|
|
5613
|
+
<div class="wf-canvas-watermark" id="wfCanvasWatermark"><img src="/icons/watermark.png" alt=""></div>
|
|
5614
|
+
<svg id="wf-canvas" xmlns="http://www.w3.org/2000/svg" ondragover="event.preventDefault()" ondrop="wfCanvasDrop(event)"></svg>
|
|
5615
|
+
<!-- Workflow Store Overlay -->
|
|
5616
|
+
<div class="wf-store-overlay" id="wfStoreOverlay">
|
|
5617
|
+
<div class="wf-store-header">
|
|
5618
|
+
<button class="wf-store-back" onclick="wfStoreClose()">← Library</button>
|
|
5619
|
+
<div class="wf-store-title-area">
|
|
5620
|
+
<span class="wf-store-title">Workflow Store</span>
|
|
5621
|
+
<span class="wf-store-badge">@vaicli</span>
|
|
5622
|
+
</div>
|
|
5623
|
+
<div class="wf-store-search-wrap">
|
|
5624
|
+
<span class="wf-store-search-icon">⌕</span>
|
|
5625
|
+
<input class="wf-store-search" id="wfStoreSearch" placeholder="Search..." oninput="wfStoreRender()">
|
|
5626
|
+
</div>
|
|
5627
|
+
<select class="wf-store-sort" id="wfStoreSort" onchange="wfStoreRender()">
|
|
5628
|
+
<option value="downloads">Popular</option>
|
|
5629
|
+
<option value="name">Name A–Z</option>
|
|
5630
|
+
<option value="complexity">Complex</option>
|
|
5631
|
+
</select>
|
|
5632
|
+
</div>
|
|
5633
|
+
<div class="wf-store-body">
|
|
5634
|
+
<div class="wf-store-inner" id="wfStoreContent">
|
|
5635
|
+
<div style="padding:40px;text-align:center;color:var(--text-muted);">Loading catalog...</div>
|
|
5636
|
+
</div>
|
|
5637
|
+
</div>
|
|
5638
|
+
</div>
|
|
4384
5639
|
</div>
|
|
4385
5640
|
<div class="wf-inspector collapsed" id="wfInspector">
|
|
4386
5641
|
<button class="wf-inspector-toggle" id="wfInspectorToggle" onclick="wfToggleInspector()" title="Toggle inspector">‹</button>
|
|
@@ -4411,6 +5666,21 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4411
5666
|
</div>
|
|
4412
5667
|
</div>
|
|
4413
5668
|
|
|
5669
|
+
<!-- ── Workflow Input Modal (pre-execution) ── -->
|
|
5670
|
+
<div class="wf-input-modal-backdrop" id="wfInputModalBackdrop" style="display:none;" onclick="wfCloseInputModal()">
|
|
5671
|
+
<div class="wf-input-modal" onclick="event.stopPropagation()">
|
|
5672
|
+
<div class="wf-input-modal-header">
|
|
5673
|
+
<span class="wf-input-modal-title" id="wfInputModalTitle">Workflow Inputs</span>
|
|
5674
|
+
<button class="wf-output-modal-btn close" onclick="wfCloseInputModal()" title="Close">×</button>
|
|
5675
|
+
</div>
|
|
5676
|
+
<div class="wf-input-modal-body" id="wfInputModalBody"></div>
|
|
5677
|
+
<div class="wf-input-modal-footer">
|
|
5678
|
+
<button class="wf-input-modal-cancel" onclick="wfCloseInputModal()">Cancel</button>
|
|
5679
|
+
<button class="wf-input-modal-run" onclick="wfInputModalSubmit()">Run Workflow</button>
|
|
5680
|
+
</div>
|
|
5681
|
+
</div>
|
|
5682
|
+
</div>
|
|
5683
|
+
|
|
4414
5684
|
<!-- ========== ABOUT TAB ========== -->
|
|
4415
5685
|
<div class="tab-panel" id="tab-about" role="tabpanel" aria-labelledby="tab-btn-about" tabindex="0">
|
|
4416
5686
|
<div class="about-container">
|
|
@@ -4421,9 +5691,9 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4421
5691
|
<div class="about-name">Michael Lynn</div>
|
|
4422
5692
|
<div class="about-role">Principal Staff Developer Advocate · MongoDB</div>
|
|
4423
5693
|
<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"
|
|
5694
|
+
<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>
|
|
5695
|
+
<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>
|
|
5696
|
+
<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
5697
|
</div>
|
|
4428
5698
|
</div>
|
|
4429
5699
|
</div>
|
|
@@ -4490,13 +5760,39 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4490
5760
|
<div class="card" style="margin-top:16px;">
|
|
4491
5761
|
<div class="about-section" style="padding-bottom:0;">
|
|
4492
5762
|
<div class="about-section-title">What's New</div>
|
|
4493
|
-
<div class="about-
|
|
4494
|
-
<
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
<
|
|
4499
|
-
|
|
5763
|
+
<div class="about-changelog">
|
|
5764
|
+
<details open>
|
|
5765
|
+
<summary><strong>v1.28</strong> — Lucide icon overhaul</summary>
|
|
5766
|
+
<p>Replaced all emoji and filled LeafyGreen icons with clean, stroke-based Lucide SVGs across the entire app — tabs, workflow nodes, action buttons, benchmark page, explore cards, settings, and more. Added reusable icon helper and path library.</p>
|
|
5767
|
+
</details>
|
|
5768
|
+
<details>
|
|
5769
|
+
<summary><strong>v1.27</strong> — Workflow builder improvements</summary>
|
|
5770
|
+
<p>Workflow palette with drag-and-drop node creation, edge drawing, dry-run preview, and builder mode toggle. New workflow node types: query, rerank, search, ingest.</p>
|
|
5771
|
+
</details>
|
|
5772
|
+
<details>
|
|
5773
|
+
<summary><strong>v1.26</strong> — Agent workflows</summary>
|
|
5774
|
+
<p>Agent workflows with thinking panel & markdown rendering, multi-step tool orchestration, interactive DAG visualization with execution animation.</p>
|
|
5775
|
+
</details>
|
|
5776
|
+
<details>
|
|
5777
|
+
<summary><strong>v1.25</strong> — Code generation & scaffolding</summary>
|
|
5778
|
+
<p>Generate embedding/search code snippets and scaffold full project directories with best-practice structure, ready-to-run RAG pipelines.</p>
|
|
5779
|
+
</details>
|
|
5780
|
+
<details>
|
|
5781
|
+
<summary><strong>v1.24</strong> — MCP server management</summary>
|
|
5782
|
+
<p>MCP server install/uninstall/status commands. Electron app v1.5 with signed macOS builds.</p>
|
|
5783
|
+
</details>
|
|
5784
|
+
<details>
|
|
5785
|
+
<summary><strong>v1.23</strong> — MCP server</summary>
|
|
5786
|
+
<p>Expose vai tools to AI agents via MCP protocol. HTTP transport, bearer auth, 71+ MCP tests.</p>
|
|
5787
|
+
</details>
|
|
5788
|
+
<details>
|
|
5789
|
+
<summary><strong>v1.22</strong> — RAG chat</summary>
|
|
5790
|
+
<p>RAG chat with smart source labels, configurable system prompts, streaming responses.</p>
|
|
5791
|
+
</details>
|
|
5792
|
+
<details>
|
|
5793
|
+
<summary><strong>v1.2</strong> — Multimodal & more</summary>
|
|
5794
|
+
<p>Multimodal tab, 4 new Explore concepts, auto-update, hidden easter egg <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;"><rect width="14" height="20" x="5" y="2" rx="2" ry="2"/><path d="M12 18h.01"/></svg></p>
|
|
5795
|
+
</details>
|
|
4500
5796
|
</div>
|
|
4501
5797
|
</div>
|
|
4502
5798
|
</div>
|
|
@@ -4519,11 +5815,11 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4519
5815
|
<!-- Mode Toggle -->
|
|
4520
5816
|
<div class="subtabs" role="tablist">
|
|
4521
5817
|
<button class="subtab active" id="genModeCode" onclick="setGenerateMode('code')" role="tab" aria-selected="true">
|
|
4522
|
-
<svg viewBox="0 0
|
|
5818
|
+
<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
5819
|
Generate Code
|
|
4524
5820
|
</button>
|
|
4525
5821
|
<button class="subtab" id="genModeScaffold" onclick="setGenerateMode('scaffold')" role="tab" aria-selected="false">
|
|
4526
|
-
<svg viewBox="0 0
|
|
5822
|
+
<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
5823
|
Scaffold Project
|
|
4528
5824
|
</button>
|
|
4529
5825
|
</div>
|
|
@@ -4665,7 +5961,7 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4665
5961
|
</div>
|
|
4666
5962
|
<div id="scaffoldWebButtons" style="display:none;">
|
|
4667
5963
|
<button class="btn btn-primary" onclick="downloadScaffoldZip()" id="scaffoldDownloadBtn">
|
|
4668
|
-
<svg width="16" height="16" viewBox="0 0
|
|
5964
|
+
<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
5965
|
Download ZIP
|
|
4670
5966
|
</button>
|
|
4671
5967
|
</div>
|
|
@@ -4722,31 +6018,31 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4722
6018
|
<nav class="settings-nav">
|
|
4723
6019
|
<div class="settings-nav-header">Settings</div>
|
|
4724
6020
|
<button class="settings-nav-item active" data-settings-section="general">
|
|
4725
|
-
<svg width="16" height="16" viewBox="0 0
|
|
6021
|
+
<svg width="16" height="16" viewBox="0 0 24 24"><use href="#lg-config"/></svg>
|
|
4726
6022
|
<span>General</span>
|
|
4727
6023
|
</button>
|
|
4728
6024
|
<button class="settings-nav-item" data-settings-section="appearance">
|
|
4729
|
-
<svg width="16" height="16" viewBox="0 0
|
|
6025
|
+
<svg width="16" height="16" viewBox="0 0 24 24"><use href="#lg-palette"/></svg>
|
|
4730
6026
|
<span>Appearance</span>
|
|
4731
6027
|
</button>
|
|
4732
6028
|
<button class="settings-nav-item" data-settings-section="models">
|
|
4733
|
-
<svg width="16" height="16" viewBox="0 0
|
|
6029
|
+
<svg width="16" height="16" viewBox="0 0 24 24"><use href="#lg-cube"/></svg>
|
|
4734
6030
|
<span>Models</span>
|
|
4735
6031
|
</button>
|
|
4736
6032
|
<button class="settings-nav-item" data-settings-section="chat">
|
|
4737
|
-
<svg width="16" height="16" viewBox="0 0
|
|
6033
|
+
<svg width="16" height="16" viewBox="0 0 24 24"><use href="#lg-chat"/></svg>
|
|
4738
6034
|
<span>Chat</span>
|
|
4739
6035
|
</button>
|
|
4740
6036
|
<button class="settings-nav-item" data-settings-section="benchmark">
|
|
4741
|
-
<svg width="16" height="16" viewBox="0 0
|
|
6037
|
+
<svg width="16" height="16" viewBox="0 0 24 24"><use href="#lg-gauge"/></svg>
|
|
4742
6038
|
<span>Benchmark</span>
|
|
4743
6039
|
</button>
|
|
4744
6040
|
<button class="settings-nav-item" data-settings-section="privacy">
|
|
4745
|
-
<svg width="16" height="16" viewBox="0 0
|
|
6041
|
+
<svg width="16" height="16" viewBox="0 0 24 24"><use href="#lg-shield"/></svg>
|
|
4746
6042
|
<span>Data & Privacy</span>
|
|
4747
6043
|
</button>
|
|
4748
6044
|
<button class="settings-nav-item" data-settings-section="health">
|
|
4749
|
-
<svg width="16" height="16" viewBox="0 0
|
|
6045
|
+
<svg width="16" height="16" viewBox="0 0 24 24"><use href="#lg-pulse"/></svg>
|
|
4750
6046
|
<span>Health Check</span>
|
|
4751
6047
|
</button>
|
|
4752
6048
|
</nav>
|
|
@@ -4768,12 +6064,12 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4768
6064
|
<div class="settings-row">
|
|
4769
6065
|
<div class="settings-label">
|
|
4770
6066
|
<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"
|
|
6067
|
+
<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
6068
|
</div>
|
|
4773
6069
|
<div class="settings-control" style="min-width:260px;">
|
|
4774
6070
|
<div class="settings-api-field">
|
|
4775
6071
|
<input type="password" id="settingsApiKey" placeholder="pa-..." autocomplete="off" spellcheck="false">
|
|
4776
|
-
<button type="button" id="settingsApiKeyToggle" title="Show/hide key"
|
|
6072
|
+
<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
6073
|
<button type="button" id="settingsApiKeySave" class="save-btn" title="Save key">Save</button>
|
|
4778
6074
|
</div>
|
|
4779
6075
|
</div>
|
|
@@ -4801,6 +6097,26 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4801
6097
|
</select>
|
|
4802
6098
|
</div>
|
|
4803
6099
|
</div>
|
|
6100
|
+
<div class="settings-row">
|
|
6101
|
+
<div class="settings-label">
|
|
6102
|
+
<span class="settings-label-text">Default Tab</span>
|
|
6103
|
+
<span class="settings-label-hint">Which tab to open when the app starts</span>
|
|
6104
|
+
</div>
|
|
6105
|
+
<div class="settings-control">
|
|
6106
|
+
<select class="settings-select" id="settingsDefaultTab">
|
|
6107
|
+
<option value="home" selected>Home</option>
|
|
6108
|
+
<option value="embed">Embed</option>
|
|
6109
|
+
<option value="compare">Compare</option>
|
|
6110
|
+
<option value="search">Search</option>
|
|
6111
|
+
<option value="multimodal">Multimodal</option>
|
|
6112
|
+
<option value="generate">Generate</option>
|
|
6113
|
+
<option value="chat">Chat</option>
|
|
6114
|
+
<option value="workflows">Workflows</option>
|
|
6115
|
+
<option value="benchmark">Benchmark</option>
|
|
6116
|
+
<option value="explore">Explore</option>
|
|
6117
|
+
</select>
|
|
6118
|
+
</div>
|
|
6119
|
+
</div>
|
|
4804
6120
|
</div>
|
|
4805
6121
|
</div>
|
|
4806
6122
|
|
|
@@ -4927,7 +6243,7 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4927
6243
|
title="Show/hide API key"
|
|
4928
6244
|
style="padding:8px 12px;min-width:auto"
|
|
4929
6245
|
>
|
|
4930
|
-
|
|
6246
|
+
<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
6247
|
</button>
|
|
4932
6248
|
<button
|
|
4933
6249
|
class="btn"
|
|
@@ -4936,7 +6252,7 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
4936
6252
|
title="Save API key"
|
|
4937
6253
|
style="padding:8px 12px;min-width:auto"
|
|
4938
6254
|
>
|
|
4939
|
-
|
|
6255
|
+
<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
6256
|
</button>
|
|
4941
6257
|
</div>
|
|
4942
6258
|
</div>
|
|
@@ -5289,78 +6605,452 @@ async function init() {
|
|
|
5289
6605
|
populateModelSelects();
|
|
5290
6606
|
buildExploreCards();
|
|
5291
6607
|
|
|
6608
|
+
// Apply default tab setting
|
|
6609
|
+
const settings = JSON.parse(localStorage.getItem('vai-settings') || '{}');
|
|
6610
|
+
const defaultTab = settings.defaultTab || 'home';
|
|
6611
|
+
switchTab(defaultTab);
|
|
6612
|
+
|
|
5292
6613
|
// Telemetry
|
|
5293
6614
|
initTelemetryToggle();
|
|
5294
6615
|
sendTelemetry('app_launch');
|
|
5295
6616
|
}
|
|
5296
6617
|
|
|
5297
|
-
// ── Tabs ──
|
|
5298
|
-
function setupTabs() {
|
|
5299
|
-
const tabBtns = document.querySelectorAll('.tab-btn');
|
|
5300
|
-
const tabList = Array.from(tabBtns);
|
|
6618
|
+
// ── Tabs ──
|
|
6619
|
+
function setupTabs() {
|
|
6620
|
+
const tabBtns = document.querySelectorAll('.tab-btn');
|
|
6621
|
+
const tabList = Array.from(tabBtns);
|
|
6622
|
+
|
|
6623
|
+
tabBtns.forEach(btn => {
|
|
6624
|
+
btn.addEventListener('click', () => {
|
|
6625
|
+
switchTab(btn.dataset.tab);
|
|
6626
|
+
});
|
|
6627
|
+
|
|
6628
|
+
// Keyboard navigation for accessibility
|
|
6629
|
+
btn.addEventListener('keydown', (e) => {
|
|
6630
|
+
const currentIndex = tabList.indexOf(btn);
|
|
6631
|
+
let nextIndex = -1;
|
|
6632
|
+
|
|
6633
|
+
if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
|
|
6634
|
+
e.preventDefault();
|
|
6635
|
+
nextIndex = (currentIndex + 1) % tabList.length;
|
|
6636
|
+
} else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
|
|
6637
|
+
e.preventDefault();
|
|
6638
|
+
nextIndex = (currentIndex - 1 + tabList.length) % tabList.length;
|
|
6639
|
+
} else if (e.key === 'Home') {
|
|
6640
|
+
e.preventDefault();
|
|
6641
|
+
nextIndex = 0;
|
|
6642
|
+
} else if (e.key === 'End') {
|
|
6643
|
+
e.preventDefault();
|
|
6644
|
+
nextIndex = tabList.length - 1;
|
|
6645
|
+
}
|
|
6646
|
+
|
|
6647
|
+
if (nextIndex >= 0) {
|
|
6648
|
+
tabList[nextIndex].focus();
|
|
6649
|
+
switchTab(tabList[nextIndex].dataset.tab);
|
|
6650
|
+
}
|
|
6651
|
+
});
|
|
6652
|
+
});
|
|
6653
|
+
|
|
6654
|
+
// Settings cog in header
|
|
6655
|
+
const settingsCog = document.querySelector('.sidebar-settings-btn');
|
|
6656
|
+
if (settingsCog) {
|
|
6657
|
+
settingsCog.addEventListener('click', () => {
|
|
6658
|
+
switchTab(settingsCog.dataset.tab);
|
|
6659
|
+
});
|
|
6660
|
+
}
|
|
6661
|
+
}
|
|
6662
|
+
|
|
6663
|
+
function switchTab(tab) {
|
|
6664
|
+
document.querySelectorAll('.tab-btn').forEach(b => {
|
|
6665
|
+
const isActive = b.dataset.tab === tab;
|
|
6666
|
+
b.classList.toggle('active', isActive);
|
|
6667
|
+
// Update ARIA attributes for accessibility
|
|
6668
|
+
b.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
|
6669
|
+
b.setAttribute('tabindex', isActive ? '0' : '-1');
|
|
6670
|
+
});
|
|
6671
|
+
document.querySelectorAll('.tab-panel').forEach(p => {
|
|
6672
|
+
const isActive = p.id === 'tab-' + tab;
|
|
6673
|
+
p.classList.toggle('active', isActive);
|
|
6674
|
+
// Hide inactive panels from screen readers
|
|
6675
|
+
p.setAttribute('aria-hidden', isActive ? 'false' : 'true');
|
|
6676
|
+
});
|
|
6677
|
+
// Sync settings cog highlight
|
|
6678
|
+
const settingsBtn = document.querySelector('.sidebar-settings-btn');
|
|
6679
|
+
if (settingsBtn) settingsBtn.classList.toggle('active', tab === 'settings');
|
|
6680
|
+
// Track tab views
|
|
6681
|
+
sendTelemetry('tab_view', { tab });
|
|
6682
|
+
|
|
6683
|
+
// Initialize Home page if switching to it
|
|
6684
|
+
if (tab === 'home') {
|
|
6685
|
+
homeInit();
|
|
6686
|
+
}
|
|
6687
|
+
}
|
|
6688
|
+
// Expose globally so Electron main process can call it
|
|
6689
|
+
window.switchTab = switchTab;
|
|
6690
|
+
|
|
6691
|
+
// ── Home Page ──
|
|
6692
|
+
|
|
6693
|
+
let homeData = {
|
|
6694
|
+
announcements: null,
|
|
6695
|
+
releases: null,
|
|
6696
|
+
featuredWorkflows: null,
|
|
6697
|
+
communityWorkflows: null,
|
|
6698
|
+
currentAnnouncementIndex: 0,
|
|
6699
|
+
sortMode: 'trending'
|
|
6700
|
+
};
|
|
6701
|
+
|
|
6702
|
+
async function homeInit() {
|
|
6703
|
+
// Only initialize once per session
|
|
6704
|
+
if (homeData.announcements !== null) return;
|
|
6705
|
+
|
|
6706
|
+
try {
|
|
6707
|
+
// Load version info
|
|
6708
|
+
await updateHomeVersionInfo();
|
|
6709
|
+
|
|
6710
|
+
// Check API key status and show warning if needed
|
|
6711
|
+
checkApiKeyStatus();
|
|
6712
|
+
|
|
6713
|
+
// Load announcements and releases first (fast, local data)
|
|
6714
|
+
await Promise.all([
|
|
6715
|
+
loadAnnouncements(),
|
|
6716
|
+
loadReleases(),
|
|
6717
|
+
]);
|
|
6718
|
+
|
|
6719
|
+
// Render available sections immediately
|
|
6720
|
+
renderAnnouncements();
|
|
6721
|
+
renderReleases();
|
|
6722
|
+
|
|
6723
|
+
// Load marketplace data in background (slower, network-dependent)
|
|
6724
|
+
loadMarketplaceData().then(() => {
|
|
6725
|
+
renderFeaturedWorkflows();
|
|
6726
|
+
renderCommunityWorkflows();
|
|
6727
|
+
}).catch(() => {});
|
|
6728
|
+
|
|
6729
|
+
} catch (err) {
|
|
6730
|
+
console.error('Failed to initialize Home page:', err);
|
|
6731
|
+
}
|
|
6732
|
+
}
|
|
6733
|
+
|
|
6734
|
+
async function updateHomeVersionInfo() {
|
|
6735
|
+
let version = window.appVersion;
|
|
6736
|
+
|
|
6737
|
+
// If version not set yet, try to fetch from Electron
|
|
6738
|
+
if (!version && window.vai && window.vai.getVersion) {
|
|
6739
|
+
try {
|
|
6740
|
+
const v = await window.vai.getVersion();
|
|
6741
|
+
if (v) {
|
|
6742
|
+
version = typeof v === 'object' ? v.app : v;
|
|
6743
|
+
window.appVersion = version;
|
|
6744
|
+
}
|
|
6745
|
+
} catch (e) {
|
|
6746
|
+
console.warn('Failed to get version:', e);
|
|
6747
|
+
}
|
|
6748
|
+
}
|
|
6749
|
+
|
|
6750
|
+
// Fallback for web mode: try to get from CLI package.json via API
|
|
6751
|
+
if (!version) {
|
|
6752
|
+
try {
|
|
6753
|
+
const res = await fetch('/api/version');
|
|
6754
|
+
if (res.ok) {
|
|
6755
|
+
const data = await res.json();
|
|
6756
|
+
version = data.version || data.app;
|
|
6757
|
+
if (version) window.appVersion = version;
|
|
6758
|
+
}
|
|
6759
|
+
} catch (e) {
|
|
6760
|
+
// Ignore fetch errors in web mode
|
|
6761
|
+
}
|
|
6762
|
+
}
|
|
6763
|
+
|
|
6764
|
+
version = version || 'dev';
|
|
6765
|
+
document.getElementById('homeVersionBadge').textContent = `v${version}`;
|
|
6766
|
+
const footerEl = document.getElementById('homeFooterVersion');
|
|
6767
|
+
if (footerEl) footerEl.textContent = `Version ${version} • © 2026 VAI`;
|
|
6768
|
+
}
|
|
6769
|
+
|
|
6770
|
+
async function checkApiKeyStatus() {
|
|
6771
|
+
try {
|
|
6772
|
+
const res = await fetch('/api/config');
|
|
6773
|
+
const data = await res.json();
|
|
6774
|
+
const warningEl = document.getElementById('homeApiWarning');
|
|
6775
|
+
|
|
6776
|
+
if (!data.hasKey) {
|
|
6777
|
+
warningEl.style.display = 'block';
|
|
6778
|
+
} else {
|
|
6779
|
+
warningEl.style.display = 'none';
|
|
6780
|
+
}
|
|
6781
|
+
} catch {
|
|
6782
|
+
// If config check fails, assume no API key
|
|
6783
|
+
document.getElementById('homeApiWarning').style.display = 'block';
|
|
6784
|
+
}
|
|
6785
|
+
}
|
|
6786
|
+
|
|
6787
|
+
async function loadAnnouncements() {
|
|
6788
|
+
try {
|
|
6789
|
+
const res = await fetch('/api/home/announcements');
|
|
6790
|
+
const data = await res.json();
|
|
6791
|
+
|
|
6792
|
+
// Filter out dismissed announcements
|
|
6793
|
+
const dismissed = JSON.parse(localStorage.getItem('vai-dismissed-announcements') || '[]');
|
|
6794
|
+
homeData.announcements = data.announcements.filter(a => !dismissed.includes(a.id));
|
|
6795
|
+
|
|
6796
|
+
} catch (err) {
|
|
6797
|
+
console.error('Failed to load announcements:', err);
|
|
6798
|
+
homeData.announcements = [];
|
|
6799
|
+
}
|
|
6800
|
+
}
|
|
6801
|
+
|
|
6802
|
+
async function loadReleases() {
|
|
6803
|
+
try {
|
|
6804
|
+
// Try cache first
|
|
6805
|
+
const cached = localStorage.getItem('vai-releases-cache');
|
|
6806
|
+
if (cached) {
|
|
6807
|
+
const { data, timestamp } = JSON.parse(cached);
|
|
6808
|
+
// Use cache if less than 30 minutes old AND it has real data (not fallback)
|
|
6809
|
+
const hasRealData = data && data.length > 0 && !data[0].version?.includes('1.0.0');
|
|
6810
|
+
if (hasRealData && Date.now() - timestamp < 30 * 60 * 1000) {
|
|
6811
|
+
homeData.releases = data;
|
|
6812
|
+
return;
|
|
6813
|
+
}
|
|
6814
|
+
}
|
|
6815
|
+
|
|
6816
|
+
const res = await fetch('/api/home/releases');
|
|
6817
|
+
const data = await res.json();
|
|
6818
|
+
homeData.releases = data.releases;
|
|
6819
|
+
|
|
6820
|
+
// Only cache if we got real data (not fallback)
|
|
6821
|
+
const hasRealData = data.releases && data.releases.length > 0 && !data.releases[0].version?.includes('1.0.0');
|
|
6822
|
+
if (hasRealData) {
|
|
6823
|
+
localStorage.setItem('vai-releases-cache', JSON.stringify({
|
|
6824
|
+
data: data.releases,
|
|
6825
|
+
timestamp: Date.now()
|
|
6826
|
+
}));
|
|
6827
|
+
} else {
|
|
6828
|
+
// Clear stale cache if we got fallback data
|
|
6829
|
+
localStorage.removeItem('vai-releases-cache');
|
|
6830
|
+
}
|
|
6831
|
+
|
|
6832
|
+
} catch (err) {
|
|
6833
|
+
console.error('Failed to load releases:', err);
|
|
6834
|
+
homeData.releases = [];
|
|
6835
|
+
}
|
|
6836
|
+
}
|
|
6837
|
+
|
|
6838
|
+
async function loadMarketplaceData() {
|
|
6839
|
+
try {
|
|
6840
|
+
const res = await fetch('/api/workflows/catalog');
|
|
6841
|
+
const data = await res.json();
|
|
6842
|
+
|
|
6843
|
+
// Extract featured and community workflows
|
|
6844
|
+
homeData.featuredWorkflows = data.workflows.filter(w => w.featured).slice(0, 4);
|
|
6845
|
+
homeData.communityWorkflows = data.workflows.filter(w => !w.featured);
|
|
6846
|
+
|
|
6847
|
+
} catch (err) {
|
|
6848
|
+
console.error('Failed to load marketplace data:', err);
|
|
6849
|
+
homeData.featuredWorkflows = [];
|
|
6850
|
+
homeData.communityWorkflows = [];
|
|
6851
|
+
}
|
|
6852
|
+
}
|
|
6853
|
+
|
|
6854
|
+
function renderAnnouncements() {
|
|
6855
|
+
const container = document.getElementById('homeAnnouncements');
|
|
6856
|
+
const carousel = document.getElementById('announcementsCarousel');
|
|
6857
|
+
const dots = document.getElementById('announcementsDots');
|
|
6858
|
+
|
|
6859
|
+
if (!homeData.announcements.length) {
|
|
6860
|
+
container.style.display = 'none';
|
|
6861
|
+
return;
|
|
6862
|
+
}
|
|
6863
|
+
|
|
6864
|
+
// Render cards
|
|
6865
|
+
carousel.innerHTML = homeData.announcements.map((ann, i) => `
|
|
6866
|
+
<div class="home-announcement-card${i === 0 ? ' active' : ''}" data-id="${ann.id}">
|
|
6867
|
+
<button class="home-announcement-dismiss" onclick="dismissAnnouncement('${ann.id}')">×</button>
|
|
6868
|
+
${ann.badge ? `<div class="badge">${ann.badge}</div>` : ''}
|
|
6869
|
+
<h3>${ann.title}</h3>
|
|
6870
|
+
<p>${ann.description}</p>
|
|
6871
|
+
${ann.cta ? `<button class="cta" onclick="${ann.cta.action === 'navigate' ? `switchTab('${ann.cta.target.slice(1)}')` : 'void(0)'}">${ann.cta.label}</button>` : ''}
|
|
6872
|
+
</div>
|
|
6873
|
+
`).join('');
|
|
6874
|
+
|
|
6875
|
+
// Render dots
|
|
6876
|
+
if (homeData.announcements.length > 1) {
|
|
6877
|
+
dots.innerHTML = homeData.announcements.map((_, i) =>
|
|
6878
|
+
`<div class="home-announcement-dot${i === 0 ? ' active' : ''}" onclick="showAnnouncement(${i})"></div>`
|
|
6879
|
+
).join('');
|
|
6880
|
+
|
|
6881
|
+
// Start rotation
|
|
6882
|
+
startAnnouncementRotation();
|
|
6883
|
+
}
|
|
6884
|
+
|
|
6885
|
+
container.style.display = 'block';
|
|
6886
|
+
}
|
|
6887
|
+
|
|
6888
|
+
function showAnnouncement(index) {
|
|
6889
|
+
homeData.currentAnnouncementIndex = index;
|
|
6890
|
+
|
|
6891
|
+
// Update cards
|
|
6892
|
+
document.querySelectorAll('.home-announcement-card').forEach((card, i) => {
|
|
6893
|
+
card.classList.toggle('active', i === index);
|
|
6894
|
+
});
|
|
6895
|
+
|
|
6896
|
+
// Update dots
|
|
6897
|
+
document.querySelectorAll('.home-announcement-dot').forEach((dot, i) => {
|
|
6898
|
+
dot.classList.toggle('active', i === index);
|
|
6899
|
+
});
|
|
6900
|
+
}
|
|
6901
|
+
|
|
6902
|
+
function startAnnouncementRotation() {
|
|
6903
|
+
setInterval(() => {
|
|
6904
|
+
if (homeData.announcements.length > 1) {
|
|
6905
|
+
homeData.currentAnnouncementIndex = (homeData.currentAnnouncementIndex + 1) % homeData.announcements.length;
|
|
6906
|
+
showAnnouncement(homeData.currentAnnouncementIndex);
|
|
6907
|
+
}
|
|
6908
|
+
}, 5000); // Rotate every 5 seconds
|
|
6909
|
+
}
|
|
6910
|
+
|
|
6911
|
+
function dismissAnnouncement(id) {
|
|
6912
|
+
const dismissed = JSON.parse(localStorage.getItem('vai-dismissed-announcements') || '[]');
|
|
6913
|
+
dismissed.push(id);
|
|
6914
|
+
localStorage.setItem('vai-dismissed-announcements', JSON.stringify(dismissed));
|
|
6915
|
+
|
|
6916
|
+
// Remove from current data and re-render
|
|
6917
|
+
homeData.announcements = homeData.announcements.filter(a => a.id !== id);
|
|
6918
|
+
renderAnnouncements();
|
|
6919
|
+
}
|
|
6920
|
+
|
|
6921
|
+
function renderReleases() {
|
|
6922
|
+
const container = document.getElementById('releasesTimeline');
|
|
6923
|
+
|
|
6924
|
+
const releases = homeData.releases.slice(0, 1); // Show only the latest release
|
|
6925
|
+
container.innerHTML = releases.map(release => `
|
|
6926
|
+
<div class="home-release-item">
|
|
6927
|
+
<div>
|
|
6928
|
+
<div class="home-release-version">${release.version}</div>
|
|
6929
|
+
<div class="home-release-date">${new Date(release.date).toLocaleDateString()}</div>
|
|
6930
|
+
<ul class="home-release-highlights">
|
|
6931
|
+
${release.highlights.map(highlight => `<li>${highlight}</li>`).join('')}
|
|
6932
|
+
</ul>
|
|
6933
|
+
</div>
|
|
6934
|
+
</div>
|
|
6935
|
+
`).join('');
|
|
6936
|
+
}
|
|
6937
|
+
|
|
6938
|
+
function renderFeaturedWorkflows() {
|
|
6939
|
+
const container = document.getElementById('featuredWorkflows');
|
|
6940
|
+
|
|
6941
|
+
if (!homeData.featuredWorkflows.length) {
|
|
6942
|
+
container.innerHTML = '<div class="home-community-empty"><h4>Coming Soon</h4><p>Featured workflows launching soon</p></div>';
|
|
6943
|
+
return;
|
|
6944
|
+
}
|
|
6945
|
+
|
|
6946
|
+
container.innerHTML = homeData.featuredWorkflows.map(workflow => `
|
|
6947
|
+
<div class="home-workflow-card">
|
|
6948
|
+
<h5>${workflow.name || 'Untitled'}</h5>
|
|
6949
|
+
<p>${workflow.description || 'No description available'}</p>
|
|
6950
|
+
<div class="home-workflow-meta">
|
|
6951
|
+
<div class="home-workflow-domain">${workflow.category || 'utility'}</div>
|
|
6952
|
+
<div class="home-workflow-author">by ${workflow.author?.name || 'unknown'}</div>
|
|
6953
|
+
</div>
|
|
6954
|
+
<div class="home-workflow-actions">
|
|
6955
|
+
<button class="home-workflow-btn" onclick="installWorkflow('${workflow.packageName}')">Install</button>
|
|
6956
|
+
<button class="home-workflow-btn" onclick="viewWorkflowDetails('${workflow.packageName}')">Details</button>
|
|
6957
|
+
</div>
|
|
6958
|
+
</div>
|
|
6959
|
+
`).join('');
|
|
6960
|
+
}
|
|
6961
|
+
|
|
6962
|
+
function renderCommunityWorkflows() {
|
|
6963
|
+
const container = document.getElementById('communityWorkflows');
|
|
6964
|
+
|
|
6965
|
+
if (!homeData.communityWorkflows.length) {
|
|
6966
|
+
container.innerHTML = '<div class="home-community-empty"><h4>Be the First!</h4><p>Publish a workflow to get started</p></div>';
|
|
6967
|
+
return;
|
|
6968
|
+
}
|
|
6969
|
+
|
|
6970
|
+
let workflows = [...homeData.communityWorkflows];
|
|
6971
|
+
|
|
6972
|
+
// Sort based on current mode
|
|
6973
|
+
switch (homeData.sortMode) {
|
|
6974
|
+
case 'trending':
|
|
6975
|
+
// Use downloads as proxy for trending
|
|
6976
|
+
workflows.sort((a, b) => (b.downloads || 0) - (a.downloads || 0));
|
|
6977
|
+
break;
|
|
6978
|
+
case 'newest':
|
|
6979
|
+
// Sort by name alphabetically as fallback (no publish date available)
|
|
6980
|
+
workflows.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
|
|
6981
|
+
break;
|
|
6982
|
+
case 'installs':
|
|
6983
|
+
workflows.sort((a, b) => (b.downloads || 0) - (a.downloads || 0));
|
|
6984
|
+
break;
|
|
6985
|
+
}
|
|
6986
|
+
|
|
6987
|
+
container.innerHTML = workflows.slice(0, 6).map(workflow => `
|
|
6988
|
+
<div class="home-workflow-card">
|
|
6989
|
+
<h5>${workflow.name || 'Untitled'}</h5>
|
|
6990
|
+
<p>${workflow.description || 'No description available'}</p>
|
|
6991
|
+
<div class="home-workflow-meta">
|
|
6992
|
+
<div class="home-workflow-domain">${workflow.category || 'utility'}</div>
|
|
6993
|
+
<div class="home-workflow-author">by ${workflow.author?.name || 'unknown'}</div>
|
|
6994
|
+
</div>
|
|
6995
|
+
<div class="home-workflow-actions">
|
|
6996
|
+
<button class="home-workflow-btn" onclick="installWorkflow('${workflow.packageName}')">Install</button>
|
|
6997
|
+
<button class="home-workflow-btn" onclick="viewWorkflowDetails('${workflow.packageName}')">Details</button>
|
|
6998
|
+
</div>
|
|
6999
|
+
</div>
|
|
7000
|
+
`).join('');
|
|
7001
|
+
}
|
|
7002
|
+
|
|
7003
|
+
function sortCommunityWorkflows(mode) {
|
|
7004
|
+
homeData.sortMode = mode;
|
|
5301
7005
|
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
});
|
|
5306
|
-
|
|
5307
|
-
// Keyboard navigation for accessibility
|
|
5308
|
-
btn.addEventListener('keydown', (e) => {
|
|
5309
|
-
const currentIndex = tabList.indexOf(btn);
|
|
5310
|
-
let nextIndex = -1;
|
|
5311
|
-
|
|
5312
|
-
if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
|
|
5313
|
-
e.preventDefault();
|
|
5314
|
-
nextIndex = (currentIndex + 1) % tabList.length;
|
|
5315
|
-
} else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
|
|
5316
|
-
e.preventDefault();
|
|
5317
|
-
nextIndex = (currentIndex - 1 + tabList.length) % tabList.length;
|
|
5318
|
-
} else if (e.key === 'Home') {
|
|
5319
|
-
e.preventDefault();
|
|
5320
|
-
nextIndex = 0;
|
|
5321
|
-
} else if (e.key === 'End') {
|
|
5322
|
-
e.preventDefault();
|
|
5323
|
-
nextIndex = tabList.length - 1;
|
|
5324
|
-
}
|
|
5325
|
-
|
|
5326
|
-
if (nextIndex >= 0) {
|
|
5327
|
-
tabList[nextIndex].focus();
|
|
5328
|
-
switchTab(tabList[nextIndex].dataset.tab);
|
|
5329
|
-
}
|
|
5330
|
-
});
|
|
7006
|
+
// Update tab appearance
|
|
7007
|
+
document.querySelectorAll('.home-sort-tab').forEach(tab => {
|
|
7008
|
+
tab.classList.toggle('active', tab.dataset.sort === mode);
|
|
5331
7009
|
});
|
|
7010
|
+
|
|
7011
|
+
renderCommunityWorkflows();
|
|
7012
|
+
}
|
|
5332
7013
|
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
7014
|
+
function filterAndSwitchWorkflows(domain) {
|
|
7015
|
+
// Switch to workflows tab with domain filter
|
|
7016
|
+
switchTab('workflows');
|
|
7017
|
+
// TODO: Apply domain filter when workflows tab supports it
|
|
7018
|
+
console.log('Filtering workflows by domain:', domain);
|
|
7019
|
+
}
|
|
7020
|
+
|
|
7021
|
+
function installWorkflow(packageName) {
|
|
7022
|
+
// Use the workflow store install function
|
|
7023
|
+
wfStoreInstall(packageName, null);
|
|
7024
|
+
}
|
|
7025
|
+
|
|
7026
|
+
function viewWorkflowDetails(packageName) {
|
|
7027
|
+
// Find the workflow by packageName and show its details
|
|
7028
|
+
const allWorkflows = [...(homeData.featuredWorkflows || []), ...(homeData.communityWorkflows || [])];
|
|
7029
|
+
const wf = allWorkflows.find(w => w.packageName === packageName);
|
|
7030
|
+
if (wf) {
|
|
7031
|
+
// Use the workflow store detail modal
|
|
7032
|
+
wfStoreShowDetail(wf.name);
|
|
7033
|
+
} else {
|
|
7034
|
+
// Fallback: switch to workflows tab
|
|
7035
|
+
switchTab('workflows');
|
|
5339
7036
|
}
|
|
5340
7037
|
}
|
|
5341
7038
|
|
|
5342
|
-
function
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
document.
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
}
|
|
5356
|
-
// Sync settings cog highlight
|
|
5357
|
-
const settingsBtn = document.querySelector('.sidebar-settings-btn');
|
|
5358
|
-
if (settingsBtn) settingsBtn.classList.toggle('active', tab === 'settings');
|
|
5359
|
-
// Track tab views
|
|
5360
|
-
sendTelemetry('tab_view', { tab });
|
|
7039
|
+
function publishWorkflow() {
|
|
7040
|
+
// TODO: Open workflow publishing flow
|
|
7041
|
+
console.log('Opening workflow publisher');
|
|
7042
|
+
switchTab('workflows');
|
|
7043
|
+
}
|
|
7044
|
+
|
|
7045
|
+
function reportBug() {
|
|
7046
|
+
// Open bug report (reuse existing functionality if available)
|
|
7047
|
+
const bugButton = document.getElementById('bugButton');
|
|
7048
|
+
if (bugButton) {
|
|
7049
|
+
bugButton.click();
|
|
7050
|
+
} else {
|
|
7051
|
+
window.open('https://github.com/mrlynn/voyageai-cli/issues/new', '_blank');
|
|
7052
|
+
}
|
|
5361
7053
|
}
|
|
5362
|
-
// Expose globally so Electron main process can call it
|
|
5363
|
-
window.switchTab = switchTab;
|
|
5364
7054
|
|
|
5365
7055
|
// ── Config ──
|
|
5366
7056
|
async function loadConfig() {
|
|
@@ -5952,12 +7642,12 @@ async function downloadScaffoldZip() {
|
|
|
5952
7642
|
// Show success
|
|
5953
7643
|
btn.innerHTML = '<span style="margin-right:6px;">✓</span> Downloaded!';
|
|
5954
7644
|
setTimeout(() => {
|
|
5955
|
-
btn.innerHTML = '<svg width="16" height="16" viewBox="0 0
|
|
7645
|
+
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
7646
|
btn.disabled = false;
|
|
5957
7647
|
}, 2000);
|
|
5958
7648
|
} catch (err) {
|
|
5959
7649
|
alert('Error: ' + err.message);
|
|
5960
|
-
btn.innerHTML = '<svg width="16" height="16" viewBox="0 0
|
|
7650
|
+
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
7651
|
btn.disabled = false;
|
|
5962
7652
|
}
|
|
5963
7653
|
}
|
|
@@ -6053,37 +7743,73 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
6053
7743
|
});
|
|
6054
7744
|
|
|
6055
7745
|
// ── Explore: icons and tab mappings per concept ──
|
|
7746
|
+
// Lucide SVG icon helper — returns an inline <svg> string
|
|
7747
|
+
function lucideIcon(d, size = 16) {
|
|
7748
|
+
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>`;
|
|
7749
|
+
}
|
|
7750
|
+
|
|
7751
|
+
// Lucide path constants for concept/button icons
|
|
7752
|
+
const LI = {
|
|
7753
|
+
zap: 'M13 2 3 14h9l-1 8 10-12h-9l1-8z',
|
|
7754
|
+
search: 'M21 21l-4.3-4.3M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z',
|
|
7755
|
+
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',
|
|
7756
|
+
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',
|
|
7757
|
+
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',
|
|
7758
|
+
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',
|
|
7759
|
+
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',
|
|
7760
|
+
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',
|
|
7761
|
+
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',
|
|
7762
|
+
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',
|
|
7763
|
+
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',
|
|
7764
|
+
timer: 'M10 2h4M12 14l3-3M12 22a8 8 0 1 0 0-16 8 8 0 0 0 0 16z',
|
|
7765
|
+
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',
|
|
7766
|
+
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',
|
|
7767
|
+
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',
|
|
7768
|
+
barChart: 'M12 20V10M18 20V4M6 20v-4',
|
|
7769
|
+
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',
|
|
7770
|
+
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',
|
|
7771
|
+
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',
|
|
7772
|
+
circle: 'M12 12m-10 0a10 10 0 1 0 20 0 10 10 0 1 0-20 0',
|
|
7773
|
+
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',
|
|
7774
|
+
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',
|
|
7775
|
+
laptop: 'M20 16V7a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v9M2 20h20M12 12h.01',
|
|
7776
|
+
blocks: 'M2 12h10v10H2zM14 4l6 3.5v7L14 18l-6-3.5v-7zM12 2l10 6',
|
|
7777
|
+
refresh: 'M21 2v6h-6M3 12a9 9 0 0 1 15-6.7L21 8M3 22v-6h6M21 12a9 9 0 0 1-15 6.7L3 16',
|
|
7778
|
+
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',
|
|
7779
|
+
download: 'M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3',
|
|
7780
|
+
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',
|
|
7781
|
+
filter: 'M3 6h18M7 12h10M10 18h4',
|
|
7782
|
+
};
|
|
7783
|
+
|
|
6056
7784
|
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' },
|
|
7785
|
+
embeddings: { icon: LI.zap, tab: 'embed' },
|
|
7786
|
+
reranking: { icon: LI.trophy, tab: 'search' },
|
|
7787
|
+
'vector-search': { icon: LI.search, tab: 'search' },
|
|
7788
|
+
rag: { icon: LI.bot, tab: 'search' },
|
|
7789
|
+
'cosine-similarity': { icon: LI.ruler, tab: 'compare' },
|
|
7790
|
+
'two-stage-retrieval': { icon: LI.target, tab: 'search' },
|
|
7791
|
+
'input-type': { icon: LI.tag, tab: 'embed' },
|
|
7792
|
+
models: { icon: LI.brain, tab: 'embed' },
|
|
7793
|
+
'api-keys': { icon: LI.key, tab: 'embed' },
|
|
7794
|
+
'api-access': { icon: LI.globe, tab: 'embed' },
|
|
7795
|
+
'batch-processing': { icon: LI.package, tab: 'embed' },
|
|
7796
|
+
benchmarking: { icon: LI.timer, tab: 'benchmark' },
|
|
7797
|
+
quantization: { icon: LI.flask, tab: 'benchmark' },
|
|
7798
|
+
'mixture-of-experts': { icon: LI.puzzle, tab: 'embed' },
|
|
7799
|
+
'shared-embedding-space': { icon: LI.link, tab: 'compare' },
|
|
7800
|
+
'rteb-benchmarks': { icon: LI.barChart, tab: 'benchmark' },
|
|
7801
|
+
'voyage-4-nano': { icon: LI.microscope, tab: 'embed' },
|
|
7802
|
+
'rerank-eval': { icon: LI.ruler, tab: 'benchmark' },
|
|
7803
|
+
'multimodal-embeddings': { icon: LI.image, tab: 'multimodal' },
|
|
7804
|
+
'cross-modal-search': { icon: LI.shuffle, tab: 'multimodal' },
|
|
7805
|
+
'modality-gap': { icon: LI.circle, tab: 'multimodal' },
|
|
7806
|
+
'multimodal-rag': { icon: LI.fileTxt, tab: 'multimodal' },
|
|
7807
|
+
'provider-comparison': { icon: LI.scale, tab: 'explore' },
|
|
7808
|
+
'code-generation': { icon: LI.laptop, tab: 'explore' },
|
|
7809
|
+
scaffolding: { icon: LI.blocks, tab: 'explore' },
|
|
7810
|
+
'eval-comparison': { icon: LI.barChart, tab: 'benchmark' },
|
|
7811
|
+
'auto-embedding': { icon: LI.zap, tab: 'explore' },
|
|
7812
|
+
'vai-vs-auto-embedding': { icon: LI.refresh, tab: 'explore' },
|
|
6087
7813
|
};
|
|
6088
7814
|
|
|
6089
7815
|
let exploreConcepts = {};
|
|
@@ -6109,13 +7835,13 @@ function buildExploreCards() {
|
|
|
6109
7835
|
grid.innerHTML = '';
|
|
6110
7836
|
|
|
6111
7837
|
for (const [key, concept] of Object.entries(exploreConcepts)) {
|
|
6112
|
-
const meta = CONCEPT_META[key] || { icon:
|
|
7838
|
+
const meta = CONCEPT_META[key] || { icon: LI.package, tab: 'embed' };
|
|
6113
7839
|
const card = document.createElement('div');
|
|
6114
7840
|
card.className = 'explore-card';
|
|
6115
7841
|
card.dataset.key = key;
|
|
6116
7842
|
|
|
6117
7843
|
card.innerHTML = `
|
|
6118
|
-
<div class="explore-card-icon">${meta.icon}</div>
|
|
7844
|
+
<div class="explore-card-icon">${lucideIcon(meta.icon, 28)}</div>
|
|
6119
7845
|
<div class="explore-card-title">${escapeHtml(concept.title)}</div>
|
|
6120
7846
|
<div class="explore-card-summary">${escapeHtml(concept.summary)}</div>
|
|
6121
7847
|
`;
|
|
@@ -6142,12 +7868,12 @@ let exploreModalPreviousFocus = null;
|
|
|
6142
7868
|
function openExploreModal(key) {
|
|
6143
7869
|
const concept = exploreConcepts[key];
|
|
6144
7870
|
if (!concept) return;
|
|
6145
|
-
const meta = CONCEPT_META[key] || { icon:
|
|
7871
|
+
const meta = CONCEPT_META[key] || { icon: LI.package, tab: 'embed' };
|
|
6146
7872
|
|
|
6147
7873
|
// Save the currently focused element to restore when modal closes
|
|
6148
7874
|
exploreModalPreviousFocus = document.activeElement;
|
|
6149
7875
|
|
|
6150
|
-
document.getElementById('exploreModalIcon').
|
|
7876
|
+
document.getElementById('exploreModalIcon').innerHTML = lucideIcon(meta.icon, 32);
|
|
6151
7877
|
document.getElementById('exploreModalTitle').textContent = concept.title;
|
|
6152
7878
|
document.getElementById('exploreModalSummary').textContent = concept.summary;
|
|
6153
7879
|
|
|
@@ -7250,6 +8976,15 @@ function initSettings() {
|
|
|
7250
8976
|
timeoutSel.addEventListener('change', () => saveSetting('timeout', timeoutSel.value));
|
|
7251
8977
|
}
|
|
7252
8978
|
|
|
8979
|
+
// Default Tab
|
|
8980
|
+
const defaultTabSel = document.getElementById('settingsDefaultTab');
|
|
8981
|
+
if (defaultTabSel) {
|
|
8982
|
+
defaultTabSel.value = s.defaultTab || 'home';
|
|
8983
|
+
defaultTabSel.addEventListener('change', () => {
|
|
8984
|
+
saveSetting('defaultTab', defaultTabSel.value);
|
|
8985
|
+
});
|
|
8986
|
+
}
|
|
8987
|
+
|
|
7253
8988
|
// Benchmark iterations
|
|
7254
8989
|
const benchIterSel = document.getElementById('settingsBenchIter');
|
|
7255
8990
|
if (benchIterSel) {
|
|
@@ -9790,19 +11525,33 @@ const WF_NODE_META = {
|
|
|
9790
11525
|
search: { icon: '\u{1F50E}', label: 'Vector Search', color: '#40E0FF', category: 'retrieval' },
|
|
9791
11526
|
rerank: { icon: '\u{1F3C6}', label: 'Rerank', color: '#40E0FF', category: 'retrieval' },
|
|
9792
11527
|
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: '
|
|
11528
|
+
embed: { icon: 'M13 2 3 14h9l-1 8 10-12h-9l1-8z', label: 'Embed', color: '#B388FF', category: 'embedding' },
|
|
11529
|
+
similarity: { icon: 'M8 3 4 7l4 4M4 7h16M16 21l4-4-4-4M20 17H4', label: 'Similarity', color: '#B388FF', category: 'embedding' },
|
|
11530
|
+
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' },
|
|
11531
|
+
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' },
|
|
11532
|
+
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' },
|
|
11533
|
+
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' },
|
|
11534
|
+
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' },
|
|
11535
|
+
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' },
|
|
11536
|
+
filter: { icon: 'M22 3H2l8 9.46V19l4 2v-8.54L22 3z', label: 'Filter', color: '#90A4AE', category: 'control' },
|
|
11537
|
+
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' },
|
|
11538
|
+
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' },
|
|
11539
|
+
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' },
|
|
11540
|
+
rerank: { icon: 'M3 6h18M7 12h10M10 18h4', label: 'Rerank', color: '#CE93D8', category: 'retrieval' },
|
|
11541
|
+
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' },
|
|
11542
|
+
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' },
|
|
11543
|
+
// Phase 1-3: New workflow nodes
|
|
11544
|
+
conditional: { icon: 'M12 3l9 9-9 9-9-9z', label: 'Conditional', color: '#90A4AE', category: 'control', shape: 'diamond' },
|
|
11545
|
+
loop: { icon: 'M17 1l4 4-4 4M3 11V9a4 4 0 0 1 4-4h14M7 23l-4-4 4-4M21 13v2a4 4 0 0 1-4 4H3', label: 'Loop', color: '#90A4AE', category: 'control' },
|
|
11546
|
+
template: { icon: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8zM14 2v6h6M16 13H8M16 17H8M10 9H8', label: 'Template', color: '#90A4AE', category: 'control' },
|
|
11547
|
+
chunk: { icon: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8zM14 2v6h6M9 15h6M9 11h6M9 19h4', label: 'Chunk', color: '#00D4AA', category: 'processing' },
|
|
11548
|
+
aggregate: { icon: 'M22 3H2l8 9.46V19l4 2v-8.54L22 3z', label: 'Aggregate', color: '#00D4AA', category: 'processing' },
|
|
11549
|
+
http: { icon: 'M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zM2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z', label: 'HTTP Request', color: '#F5A623', category: 'integration' },
|
|
9804
11550
|
};
|
|
9805
11551
|
|
|
11552
|
+
// Fallback icon (gear) for unknown workflow node types
|
|
11553
|
+
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';
|
|
11554
|
+
|
|
9806
11555
|
const WF_NODE_W = 180;
|
|
9807
11556
|
const WF_NODE_H = 64;
|
|
9808
11557
|
const WF_LAYER_GAP = 260;
|
|
@@ -9812,6 +11561,7 @@ const WF_PORT_R = 5; // Port circle radius
|
|
|
9812
11561
|
|
|
9813
11562
|
let wfState = {
|
|
9814
11563
|
workflows: [],
|
|
11564
|
+
examples: [],
|
|
9815
11565
|
activeWorkflow: null,
|
|
9816
11566
|
selectedNodeId: null,
|
|
9817
11567
|
executionState: {},
|
|
@@ -9825,14 +11575,26 @@ let wfState = {
|
|
|
9825
11575
|
isPanning: false,
|
|
9826
11576
|
panStart: { x: 0, y: 0 },
|
|
9827
11577
|
executing: false,
|
|
11578
|
+
// Builder state
|
|
11579
|
+
builderMode: false,
|
|
11580
|
+
draggingEdge: null,
|
|
11581
|
+
dragNode: null,
|
|
11582
|
+
dirtyFlag: false,
|
|
9828
11583
|
};
|
|
9829
11584
|
|
|
9830
11585
|
// ── Library ──
|
|
9831
11586
|
async function wfLoadLibrary() {
|
|
9832
11587
|
try {
|
|
9833
|
-
const
|
|
9834
|
-
|
|
9835
|
-
|
|
11588
|
+
const [wfRes, exRes] = await Promise.all([
|
|
11589
|
+
fetch('/api/workflows'),
|
|
11590
|
+
fetch('/api/workflows/examples'),
|
|
11591
|
+
]);
|
|
11592
|
+
const wfData = await wfRes.json();
|
|
11593
|
+
const exData = await exRes.json();
|
|
11594
|
+
wfState.workflows = wfData.workflows || [];
|
|
11595
|
+
wfState.official = wfData.official || [];
|
|
11596
|
+
wfState.community = wfData.community || [];
|
|
11597
|
+
wfState.examples = exData.examples || [];
|
|
9836
11598
|
wfRenderLibrary();
|
|
9837
11599
|
} catch (err) {
|
|
9838
11600
|
const list = document.getElementById('wfLibraryList');
|
|
@@ -9843,19 +11605,213 @@ async function wfLoadLibrary() {
|
|
|
9843
11605
|
function wfRenderLibrary() {
|
|
9844
11606
|
const list = document.getElementById('wfLibraryList');
|
|
9845
11607
|
if (!list) return;
|
|
9846
|
-
if (wfState.workflows.length === 0) {
|
|
11608
|
+
if (wfState.workflows.length === 0 && (!wfState.examples || wfState.examples.length === 0)) {
|
|
9847
11609
|
list.innerHTML = '<div style="padding:16px;color:var(--text-muted);font-size:12px;">No workflows found</div>';
|
|
9848
11610
|
return;
|
|
9849
11611
|
}
|
|
9850
|
-
|
|
9851
|
-
|
|
9852
|
-
|
|
11612
|
+
|
|
11613
|
+
// Built-in templates
|
|
11614
|
+
let html = wfState.workflows.map(w => {
|
|
9853
11615
|
const displayName = w.name.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
9854
11616
|
return `<div class="wf-library-item" data-wf-name="${w.name}" onclick="wfSelectWorkflow('${w.name}')">
|
|
9855
11617
|
<div class="wf-library-item-name">${displayName}</div>
|
|
9856
11618
|
<div class="wf-library-item-desc">${w.description || ''}</div>
|
|
9857
11619
|
</div>`;
|
|
9858
11620
|
}).join('');
|
|
11621
|
+
|
|
11622
|
+
// Collapsible examples section
|
|
11623
|
+
const examples = wfState.examples || [];
|
|
11624
|
+
if (examples.length > 0) {
|
|
11625
|
+
html += `<div class="wf-library-section">
|
|
11626
|
+
<button class="wf-library-section-toggle" onclick="wfToggleExamples(this)">
|
|
11627
|
+
<span class="arrow">▶</span> Examples (${examples.length})
|
|
11628
|
+
</button>
|
|
11629
|
+
<div class="wf-examples-content" style="display:none;">`;
|
|
11630
|
+
|
|
11631
|
+
const categories = ['Retrieval', 'RAG', 'Ingestion', 'Analysis', 'Other'];
|
|
11632
|
+
for (const cat of categories) {
|
|
11633
|
+
const items = examples.filter(e => e.category === cat);
|
|
11634
|
+
if (items.length === 0) continue;
|
|
11635
|
+
html += `<div class="wf-library-category">${cat}</div>`;
|
|
11636
|
+
html += items.map(w => {
|
|
11637
|
+
const displayName = w.name.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
11638
|
+
return `<div class="wf-library-item" data-wf-name="${w.name}" onclick="wfSelectWorkflow('${w.name}')">
|
|
11639
|
+
<div class="wf-library-item-name">${displayName}</div>
|
|
11640
|
+
<div class="wf-library-item-desc">${w.description || ''}</div>
|
|
11641
|
+
</div>`;
|
|
11642
|
+
}).join('');
|
|
11643
|
+
}
|
|
11644
|
+
html += '</div></div>';
|
|
11645
|
+
}
|
|
11646
|
+
|
|
11647
|
+
// Official catalog section
|
|
11648
|
+
const official = wfState.official || [];
|
|
11649
|
+
html += `<div class="wf-library-section">
|
|
11650
|
+
<div class="wf-library-category" style="display:flex;align-items:center;justify-content:space-between;">
|
|
11651
|
+
<span>Official Catalog (@vaicli) (${official.length})</span>
|
|
11652
|
+
</div>`;
|
|
11653
|
+
if (official.length === 0) {
|
|
11654
|
+
html += '<div style="padding:8px 12px;color:var(--text-muted);font-size:11px;">No official workflows installed.</div>';
|
|
11655
|
+
} else {
|
|
11656
|
+
html += official.map(w => {
|
|
11657
|
+
const displayName = w.name.replace(/^@vaicli\/vai-workflow-/, '').replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
11658
|
+
const authorLine = w.author ? `<span style="color:var(--text-muted);font-size:10px;">by ${w.author}${w.version ? ' · v' + w.version : ''}</span>` : '';
|
|
11659
|
+
const tagLine = (w.tags || []).length ? `<div style="margin-top:2px;font-size:10px;color:var(--text-muted);">${w.tags.join(' · ')}</div>` : '';
|
|
11660
|
+
return `<div class="wf-library-item" data-wf-name="${w.name}" onclick="wfSelectWorkflow('${w.name}')">
|
|
11661
|
+
<div class="wf-library-item-name">✓ ${displayName}</div>
|
|
11662
|
+
<div class="wf-library-item-desc">${w.description || ''}</div>
|
|
11663
|
+
${authorLine}${tagLine}
|
|
11664
|
+
</div>`;
|
|
11665
|
+
}).join('');
|
|
11666
|
+
}
|
|
11667
|
+
html += '</div>';
|
|
11668
|
+
|
|
11669
|
+
// Community workflows section
|
|
11670
|
+
const community = wfState.community || [];
|
|
11671
|
+
html += `<div class="wf-library-section">
|
|
11672
|
+
<div class="wf-library-category" style="display:flex;align-items:center;justify-content:space-between;">
|
|
11673
|
+
<span>Community (${community.length})</span>
|
|
11674
|
+
<button onclick="wfLoadLibrary()" title="Refresh" style="background:none;border:none;cursor:pointer;color:var(--text-muted);font-size:12px;display:flex;align-items:center;"><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="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/><path d="M16 16h5v5"/></svg></button>
|
|
11675
|
+
</div>`;
|
|
11676
|
+
if (community.length === 0) {
|
|
11677
|
+
html += '<div style="padding:8px 12px;color:var(--text-muted);font-size:11px;">No community workflows installed.<br><code style="font-size:10px;">vai workflow install <name></code></div>';
|
|
11678
|
+
} else {
|
|
11679
|
+
html += community.map(w => {
|
|
11680
|
+
const displayName = w.name.replace(/^vai-workflow-/, '').replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
11681
|
+
const authorLine = w.author ? `<span style="color:var(--text-muted);font-size:10px;">by ${w.author}${w.version ? ' · v' + w.version : ''}</span>` : '';
|
|
11682
|
+
const tagLine = (w.tags || []).length ? `<div style="margin-top:2px;font-size:10px;color:var(--text-muted);">${w.tags.join(' · ')}</div>` : '';
|
|
11683
|
+
return `<div class="wf-library-item" data-wf-name="${w.name}" onclick="wfSelectWorkflow('${w.name}')">
|
|
11684
|
+
<div class="wf-library-item-name">🌐 ${displayName}</div>
|
|
11685
|
+
<div class="wf-library-item-desc">${w.description || ''}</div>
|
|
11686
|
+
${authorLine}${tagLine}
|
|
11687
|
+
</div>`;
|
|
11688
|
+
}).join('');
|
|
11689
|
+
}
|
|
11690
|
+
html += '</div>';
|
|
11691
|
+
|
|
11692
|
+
// Install from npm button
|
|
11693
|
+
html += `<div style="padding:8px 12px;">
|
|
11694
|
+
<button onclick="wfShowInstallDialog()" style="background:none;border:1px dashed var(--border);color:var(--accent);cursor:pointer;padding:6px 12px;border-radius:6px;font-size:11px;width:100%;text-align:center;">+ Install from npm</button>
|
|
11695
|
+
</div>`;
|
|
11696
|
+
|
|
11697
|
+
list.innerHTML = html;
|
|
11698
|
+
}
|
|
11699
|
+
|
|
11700
|
+
// ── Install Dialog ──
|
|
11701
|
+
function wfShowInstallDialog() {
|
|
11702
|
+
let modal = document.getElementById('wfInstallModal');
|
|
11703
|
+
if (!modal) {
|
|
11704
|
+
modal = document.createElement('div');
|
|
11705
|
+
modal.id = 'wfInstallModal';
|
|
11706
|
+
modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;z-index:9999;';
|
|
11707
|
+
modal.innerHTML = `
|
|
11708
|
+
<div style="background:var(--bg-panel);border:1px solid var(--border);border-radius:12px;padding:24px;width:480px;max-height:70vh;display:flex;flex-direction:column;">
|
|
11709
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
|
|
11710
|
+
<h3 style="margin:0;font-size:16px;">Install Workflow Package</h3>
|
|
11711
|
+
<button onclick="wfCloseInstallDialog()" style="background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:18px;">✕</button>
|
|
11712
|
+
</div>
|
|
11713
|
+
<div style="display:flex;gap:8px;margin-bottom:12px;">
|
|
11714
|
+
<input id="wfInstallSearch" type="text" placeholder="Search vai workflows on npm..." style="flex:1;padding:8px 12px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;" onkeydown="if(event.key==='Enter')wfSearchNpm()">
|
|
11715
|
+
<button onclick="wfSearchNpm()" style="padding:8px 16px;background:var(--accent);color:#fff;border:none;border-radius:6px;cursor:pointer;font-size:13px;">Search</button>
|
|
11716
|
+
</div>
|
|
11717
|
+
<div id="wfInstallResults" style="overflow-y:auto;flex:1;min-height:100px;"></div>
|
|
11718
|
+
</div>`;
|
|
11719
|
+
document.body.appendChild(modal);
|
|
11720
|
+
modal.addEventListener('click', (e) => { if (e.target === modal) wfCloseInstallDialog(); });
|
|
11721
|
+
}
|
|
11722
|
+
modal.style.display = 'flex';
|
|
11723
|
+
document.getElementById('wfInstallSearch').value = '';
|
|
11724
|
+
document.getElementById('wfInstallResults').innerHTML = '<div style="padding:16px;color:var(--text-muted);font-size:12px;text-align:center;">Search for vai-workflow packages on npm</div>';
|
|
11725
|
+
document.getElementById('wfInstallSearch').focus();
|
|
11726
|
+
}
|
|
11727
|
+
|
|
11728
|
+
function wfCloseInstallDialog() {
|
|
11729
|
+
const modal = document.getElementById('wfInstallModal');
|
|
11730
|
+
if (modal) modal.style.display = 'none';
|
|
11731
|
+
}
|
|
11732
|
+
|
|
11733
|
+
async function wfSearchNpm() {
|
|
11734
|
+
const query = document.getElementById('wfInstallSearch').value.trim();
|
|
11735
|
+
const results = document.getElementById('wfInstallResults');
|
|
11736
|
+
if (!query) return;
|
|
11737
|
+
results.innerHTML = '<div style="padding:16px;color:var(--text-muted);font-size:12px;text-align:center;">Searching...</div>';
|
|
11738
|
+
try {
|
|
11739
|
+
const res = await fetch('/api/workflows/community/search?q=' + encodeURIComponent(query) + '&limit=10');
|
|
11740
|
+
const data = await res.json();
|
|
11741
|
+
if (!data.results || data.results.length === 0) {
|
|
11742
|
+
results.innerHTML = '<div style="padding:16px;color:var(--text-muted);font-size:12px;text-align:center;">No packages found</div>';
|
|
11743
|
+
return;
|
|
11744
|
+
}
|
|
11745
|
+
results.innerHTML = data.results.map(r => {
|
|
11746
|
+
const isOfficial = r.name.startsWith('@vaicli/');
|
|
11747
|
+
const badge = isOfficial ? '<span style="background:var(--accent);color:#fff;font-size:9px;padding:1px 5px;border-radius:3px;margin-left:6px;">OFFICIAL</span>' : '';
|
|
11748
|
+
const installed = [...(wfState.official||[]), ...(wfState.community||[])].some(w => w.name === r.name);
|
|
11749
|
+
const btn = installed
|
|
11750
|
+
? '<button disabled style="padding:4px 10px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--text-muted);font-size:11px;cursor:default;">Installed</button>'
|
|
11751
|
+
: `<button onclick="wfInstallPkg('${r.name.replace(/'/g,"\\'")}')" style="padding:4px 10px;background:var(--accent);color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:11px;">Install</button>`;
|
|
11752
|
+
return `<div style="padding:10px 12px;border-bottom:1px solid var(--border);display:flex;align-items:flex-start;justify-content:space-between;">
|
|
11753
|
+
<div style="flex:1;min-width:0;">
|
|
11754
|
+
<div style="font-size:13px;font-weight:500;">${r.name}${badge}</div>
|
|
11755
|
+
<div style="font-size:11px;color:var(--text-muted);margin-top:2px;">${r.description || ''}</div>
|
|
11756
|
+
<div style="font-size:10px;color:var(--text-muted);margin-top:2px;">v${r.version || '?'}</div>
|
|
11757
|
+
</div>
|
|
11758
|
+
<div style="margin-left:12px;flex-shrink:0;">${btn}</div>
|
|
11759
|
+
</div>`;
|
|
11760
|
+
}).join('');
|
|
11761
|
+
} catch (err) {
|
|
11762
|
+
results.innerHTML = `<div style="padding:16px;color:#f44;font-size:12px;text-align:center;">Search failed: ${err.message}</div>`;
|
|
11763
|
+
}
|
|
11764
|
+
}
|
|
11765
|
+
|
|
11766
|
+
async function wfInstallPkg(name) {
|
|
11767
|
+
const results = document.getElementById('wfInstallResults');
|
|
11768
|
+
// Find the clicked button and show installing state
|
|
11769
|
+
const btns = results.querySelectorAll('button');
|
|
11770
|
+
let clickedBtn = null;
|
|
11771
|
+
btns.forEach(b => {
|
|
11772
|
+
if (b.textContent === 'Install' && !clickedBtn) {
|
|
11773
|
+
// Find the button in the same row as the package name
|
|
11774
|
+
const row = b.closest('div[style*="border-bottom"]');
|
|
11775
|
+
if (row && row.textContent.includes(name)) {
|
|
11776
|
+
clickedBtn = b;
|
|
11777
|
+
b.textContent = 'Installing...';
|
|
11778
|
+
b.disabled = true;
|
|
11779
|
+
b.style.opacity = '0.7';
|
|
11780
|
+
}
|
|
11781
|
+
}
|
|
11782
|
+
});
|
|
11783
|
+
|
|
11784
|
+
try {
|
|
11785
|
+
const res = await fetch('/api/workflows/community/install', {
|
|
11786
|
+
method: 'POST',
|
|
11787
|
+
headers: { 'Content-Type': 'application/json' },
|
|
11788
|
+
body: JSON.stringify({ name })
|
|
11789
|
+
});
|
|
11790
|
+
const text = await res.text();
|
|
11791
|
+
let data;
|
|
11792
|
+
try { data = JSON.parse(text); } catch { data = { error: text || 'Empty response' }; }
|
|
11793
|
+
if (data.success) {
|
|
11794
|
+
if (clickedBtn) { clickedBtn.textContent = '✓ Installed'; clickedBtn.style.opacity = '1'; }
|
|
11795
|
+
await wfLoadLibrary();
|
|
11796
|
+
await wfSearchNpm();
|
|
11797
|
+
} else {
|
|
11798
|
+
const errMsg = data.error || 'Unknown error';
|
|
11799
|
+
if (clickedBtn) { clickedBtn.textContent = 'Install'; clickedBtn.disabled = false; clickedBtn.style.opacity = '1'; }
|
|
11800
|
+
console.error('Install failed:', errMsg);
|
|
11801
|
+
alert('Install failed: ' + errMsg);
|
|
11802
|
+
}
|
|
11803
|
+
} catch (err) {
|
|
11804
|
+
if (clickedBtn) { clickedBtn.textContent = 'Install'; clickedBtn.disabled = false; clickedBtn.style.opacity = '1'; }
|
|
11805
|
+
console.error('Install error:', err);
|
|
11806
|
+
alert('Install failed: ' + err.message);
|
|
11807
|
+
}
|
|
11808
|
+
}
|
|
11809
|
+
|
|
11810
|
+
function wfToggleExamples(btn) {
|
|
11811
|
+
const content = btn.nextElementSibling;
|
|
11812
|
+
const isOpen = content.style.display !== 'none';
|
|
11813
|
+
content.style.display = isOpen ? 'none' : '';
|
|
11814
|
+
btn.classList.toggle('open', !isOpen);
|
|
9859
11815
|
}
|
|
9860
11816
|
|
|
9861
11817
|
async function wfSelectWorkflow(name) {
|
|
@@ -9868,12 +11824,14 @@ async function wfSelectWorkflow(name) {
|
|
|
9868
11824
|
const res = await fetch('/api/workflows/' + encodeURIComponent(name));
|
|
9869
11825
|
const data = await res.json();
|
|
9870
11826
|
wfState.activeWorkflow = data.definition;
|
|
11827
|
+
wfState.builderMode = false;
|
|
9871
11828
|
wfState.selectedNodeId = null;
|
|
9872
11829
|
wfState.executionState = {};
|
|
9873
11830
|
wfState.executionResults = {};
|
|
9874
11831
|
wfSetToolbarEnabled(true);
|
|
9875
11832
|
document.getElementById('wfCanvasEmpty').style.display = 'none';
|
|
9876
11833
|
await wfRenderWorkflow(data.definition);
|
|
11834
|
+
wfSwitchLibTab('library');
|
|
9877
11835
|
wfOpenInspector();
|
|
9878
11836
|
wfUpdateInspector();
|
|
9879
11837
|
} catch (err) {
|
|
@@ -9992,6 +11950,19 @@ async function wfRenderWorkflow(definition) {
|
|
|
9992
11950
|
});
|
|
9993
11951
|
}
|
|
9994
11952
|
|
|
11953
|
+
// Build conditional branch maps for edge styling
|
|
11954
|
+
const elseBranchEdges = new Set(); // "fromId->toId" for else branches
|
|
11955
|
+
if (definition.steps) {
|
|
11956
|
+
for (const step of definition.steps) {
|
|
11957
|
+
if (step.tool === 'conditional' && step.inputs) {
|
|
11958
|
+
const elseSteps = step.inputs.else || [];
|
|
11959
|
+
for (const eId of elseSteps) {
|
|
11960
|
+
elseBranchEdges.add(`${step.id}->${eId}`);
|
|
11961
|
+
}
|
|
11962
|
+
}
|
|
11963
|
+
}
|
|
11964
|
+
}
|
|
11965
|
+
|
|
9995
11966
|
// Draw edges first (behind nodes)
|
|
9996
11967
|
const edgeGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
9997
11968
|
edgeGroup.classList.add('wf-edge-group');
|
|
@@ -10002,6 +11973,15 @@ async function wfRenderWorkflow(definition) {
|
|
|
10002
11973
|
const depId = rawDepId.replace(/^!/, '');
|
|
10003
11974
|
if (positions[depId] && positions[stepId]) {
|
|
10004
11975
|
const edge = wfDrawEdge(depId, stepId, positions);
|
|
11976
|
+
// Mark else-branch edges with dashed style
|
|
11977
|
+
const edgeKey = `${depId}->${stepId}`;
|
|
11978
|
+
if (elseBranchEdges.has(edgeKey)) {
|
|
11979
|
+
edge.querySelector('.wf-edge')?.classList.add('wf-edge--else');
|
|
11980
|
+
}
|
|
11981
|
+
// Mark edges to skipped nodes
|
|
11982
|
+
if (wfState.executionState[stepId] === 'skipped') {
|
|
11983
|
+
edge.querySelector('.wf-edge')?.classList.add('wf-edge--skipped');
|
|
11984
|
+
}
|
|
10005
11985
|
edgeGroup.appendChild(edge);
|
|
10006
11986
|
}
|
|
10007
11987
|
});
|
|
@@ -10024,7 +12004,7 @@ async function wfRenderWorkflow(definition) {
|
|
|
10024
12004
|
}
|
|
10025
12005
|
|
|
10026
12006
|
function wfDrawNode(step, x, y, state, hasDeps, hasDependents) {
|
|
10027
|
-
const meta = WF_NODE_META[step.tool] || { icon:
|
|
12007
|
+
const meta = WF_NODE_META[step.tool] || { icon: WF_FALLBACK_ICON, label: step.tool, color: '#666', category: 'unknown' };
|
|
10028
12008
|
const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
10029
12009
|
g.classList.add('wf-node');
|
|
10030
12010
|
if (state !== 'idle') g.classList.add('wf-node--' + state);
|
|
@@ -10036,46 +12016,121 @@ function wfDrawNode(step, x, y, state, hasDeps, hasDependents) {
|
|
|
10036
12016
|
wfSelectNode(step.id);
|
|
10037
12017
|
});
|
|
10038
12018
|
|
|
10039
|
-
//
|
|
10040
|
-
|
|
10041
|
-
|
|
10042
|
-
|
|
10043
|
-
|
|
10044
|
-
|
|
10045
|
-
|
|
10046
|
-
|
|
10047
|
-
|
|
10048
|
-
|
|
10049
|
-
|
|
12019
|
+
// Builder: node drag
|
|
12020
|
+
if (wfState.builderMode) {
|
|
12021
|
+
g.style.cursor = 'move';
|
|
12022
|
+
g.addEventListener('mousedown', (e) => {
|
|
12023
|
+
// Don't drag if clicking a port
|
|
12024
|
+
if (e.target.classList.contains('wf-port')) return;
|
|
12025
|
+
e.stopPropagation();
|
|
12026
|
+
const svg = document.getElementById('wf-canvas');
|
|
12027
|
+
const rect = svg.getBoundingClientRect();
|
|
12028
|
+
const startSvgX = (e.clientX - rect.left) / wfState.zoom + wfState.panX;
|
|
12029
|
+
const startSvgY = (e.clientY - rect.top) / wfState.zoom + wfState.panY;
|
|
12030
|
+
const pos = wfState.nodePositions[step.id];
|
|
12031
|
+
if (!pos) return;
|
|
12032
|
+
const offX = startSvgX - pos.x;
|
|
12033
|
+
const offY = startSvgY - pos.y;
|
|
12034
|
+
wfState.dragNode = step.id;
|
|
12035
|
+
|
|
12036
|
+
function onMove(ev) {
|
|
12037
|
+
const mx = (ev.clientX - rect.left) / wfState.zoom + wfState.panX;
|
|
12038
|
+
const my = (ev.clientY - rect.top) / wfState.zoom + wfState.panY;
|
|
12039
|
+
wfState.nodePositions[step.id] = { x: mx - offX, y: my - offY };
|
|
12040
|
+
wfRefreshNodes();
|
|
12041
|
+
}
|
|
12042
|
+
function onUp() {
|
|
12043
|
+
wfState.dragNode = null;
|
|
12044
|
+
document.removeEventListener('mousemove', onMove);
|
|
12045
|
+
document.removeEventListener('mouseup', onUp);
|
|
12046
|
+
}
|
|
12047
|
+
document.addEventListener('mousemove', onMove);
|
|
12048
|
+
document.addEventListener('mouseup', onUp);
|
|
12049
|
+
});
|
|
12050
|
+
}
|
|
12051
|
+
|
|
12052
|
+
// Background shape: diamond for conditional, rounded rect for others
|
|
12053
|
+
if (meta.shape === 'diamond') {
|
|
12054
|
+
const diamond = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
|
|
12055
|
+
const cx = WF_NODE_W / 2, cy = WF_NODE_H / 2;
|
|
12056
|
+
const rx = WF_NODE_W / 2 + 8, ry = WF_NODE_H / 2 + 4;
|
|
12057
|
+
diamond.setAttribute('points', `${cx},${cy - ry} ${cx + rx},${cy} ${cx},${cy + ry} ${cx - rx},${cy}`);
|
|
12058
|
+
diamond.setAttribute('fill', meta.color);
|
|
12059
|
+
diamond.setAttribute('stroke', meta.color);
|
|
12060
|
+
diamond.setAttribute('opacity', '0.85');
|
|
12061
|
+
g.appendChild(diamond);
|
|
12062
|
+
} else {
|
|
12063
|
+
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
12064
|
+
rect.setAttribute('width', WF_NODE_W);
|
|
12065
|
+
rect.setAttribute('height', WF_NODE_H);
|
|
12066
|
+
rect.setAttribute('rx', '6');
|
|
12067
|
+
rect.setAttribute('ry', '6');
|
|
12068
|
+
rect.setAttribute('fill', meta.color);
|
|
12069
|
+
rect.setAttribute('stroke', meta.color);
|
|
12070
|
+
rect.setAttribute('opacity', '0.85');
|
|
12071
|
+
g.appendChild(rect);
|
|
12072
|
+
}
|
|
12073
|
+
|
|
12074
|
+
// Input port (left side): show in builder mode always, otherwise only if has deps
|
|
12075
|
+
const showInPort = wfState.builderMode || hasDeps;
|
|
12076
|
+
if (showInPort) {
|
|
10050
12077
|
const inPort = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
10051
12078
|
inPort.classList.add('wf-port', 'wf-port-in');
|
|
12079
|
+
if (wfState.builderMode) inPort.classList.add('wf-port-builder');
|
|
10052
12080
|
inPort.setAttribute('cx', 0);
|
|
10053
12081
|
inPort.setAttribute('cy', WF_NODE_H / 2);
|
|
10054
|
-
inPort.setAttribute('r', WF_PORT_R);
|
|
12082
|
+
inPort.setAttribute('r', wfState.builderMode ? 7 : WF_PORT_R);
|
|
10055
12083
|
inPort.setAttribute('stroke', meta.color);
|
|
10056
12084
|
inPort.setAttribute('stroke-width', '2');
|
|
12085
|
+
inPort.setAttribute('pointer-events', 'all');
|
|
12086
|
+
if (wfState.builderMode) {
|
|
12087
|
+
inPort.addEventListener('mouseup', () => {
|
|
12088
|
+
if (wfState.draggingEdge) wfEdgeDropOnInput(step.id);
|
|
12089
|
+
});
|
|
12090
|
+
}
|
|
10057
12091
|
g.appendChild(inPort);
|
|
10058
12092
|
}
|
|
10059
12093
|
|
|
10060
|
-
// Output port (right side):
|
|
10061
|
-
|
|
12094
|
+
// Output port (right side): show in builder mode always, otherwise only if has dependents
|
|
12095
|
+
const showOutPort = wfState.builderMode || hasDependents;
|
|
12096
|
+
if (showOutPort) {
|
|
10062
12097
|
const outPort = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
10063
12098
|
outPort.classList.add('wf-port', 'wf-port-out');
|
|
12099
|
+
if (wfState.builderMode) outPort.classList.add('wf-port-builder');
|
|
10064
12100
|
outPort.setAttribute('cx', WF_NODE_W);
|
|
10065
12101
|
outPort.setAttribute('cy', WF_NODE_H / 2);
|
|
10066
|
-
outPort.setAttribute('r', WF_PORT_R);
|
|
12102
|
+
outPort.setAttribute('r', wfState.builderMode ? 7 : WF_PORT_R);
|
|
10067
12103
|
outPort.setAttribute('fill', meta.color);
|
|
10068
12104
|
outPort.setAttribute('stroke-width', '2');
|
|
12105
|
+
outPort.setAttribute('pointer-events', 'all');
|
|
12106
|
+
if (wfState.builderMode) {
|
|
12107
|
+
outPort.addEventListener('mousedown', (e) => {
|
|
12108
|
+
e.stopPropagation();
|
|
12109
|
+
const pos = wfState.nodePositions[step.id];
|
|
12110
|
+
if (!pos) return;
|
|
12111
|
+
wfEdgeDragStart(step.id, pos.x + WF_NODE_W, pos.y + WF_NODE_H / 2);
|
|
12112
|
+
});
|
|
12113
|
+
}
|
|
10069
12114
|
g.appendChild(outPort);
|
|
10070
12115
|
}
|
|
10071
12116
|
|
|
10072
|
-
// Icon
|
|
10073
|
-
const
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
12117
|
+
// Icon (Lucide SVG)
|
|
12118
|
+
const iconG = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
12119
|
+
iconG.classList.add('wf-node-icon');
|
|
12120
|
+
const iconSize = 18;
|
|
12121
|
+
const ix = 22 - iconSize / 2;
|
|
12122
|
+
const iy = WF_NODE_H / 2 - iconSize / 2;
|
|
12123
|
+
iconG.setAttribute('transform', `translate(${ix},${iy}) scale(${iconSize / 24})`);
|
|
12124
|
+
const fullD = meta.icon || WF_FALLBACK_ICON;
|
|
12125
|
+
const iconPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
12126
|
+
iconPath.setAttribute('d', fullD);
|
|
12127
|
+
iconPath.setAttribute('fill', 'none');
|
|
12128
|
+
iconPath.setAttribute('stroke', 'currentColor');
|
|
12129
|
+
iconPath.setAttribute('stroke-width', '1.75');
|
|
12130
|
+
iconPath.setAttribute('stroke-linecap', 'round');
|
|
12131
|
+
iconPath.setAttribute('stroke-linejoin', 'round');
|
|
12132
|
+
iconG.appendChild(iconPath);
|
|
12133
|
+
g.appendChild(iconG);
|
|
10079
12134
|
|
|
10080
12135
|
// Label (step name, truncated)
|
|
10081
12136
|
const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
@@ -10242,44 +12297,69 @@ function wfUpdateInspector() {
|
|
|
10242
12297
|
header.textContent = def.name || 'Workflow';
|
|
10243
12298
|
let html = '';
|
|
10244
12299
|
|
|
10245
|
-
|
|
10246
|
-
|
|
12300
|
+
if (wfState.builderMode) {
|
|
12301
|
+
// Builder: editable workflow fields
|
|
12302
|
+
html += `<div class="wf-inspector-section">
|
|
12303
|
+
<div class="wf-inspector-section-title">Name</div>
|
|
12304
|
+
<input class="wf-inspector-input" value="${escapeHtml(def.name || '')}" onchange="wfEditWorkflowField('name', this.value); document.getElementById('wfInspectorHeader').textContent = this.value || 'Workflow';">
|
|
12305
|
+
</div>`;
|
|
10247
12306
|
html += `<div class="wf-inspector-section">
|
|
10248
12307
|
<div class="wf-inspector-section-title">Description</div>
|
|
10249
|
-
<
|
|
12308
|
+
<textarea class="wf-inspector-input" rows="2" style="resize:vertical;" onchange="wfEditWorkflowField('description', this.value)">${escapeHtml(def.description || '')}</textarea>
|
|
10250
12309
|
</div>`;
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
10254
|
-
|
|
10255
|
-
|
|
10256
|
-
|
|
10257
|
-
|
|
10258
|
-
|
|
10259
|
-
|
|
10260
|
-
|
|
10261
|
-
|
|
10262
|
-
|
|
12310
|
+
html += `<div class="wf-inspector-section">
|
|
12311
|
+
<div class="wf-inspector-section-title">Steps</div>
|
|
12312
|
+
<div style="font-size:12px;color:var(--text);">${def.steps.length} step${def.steps.length !== 1 ? 's' : ''}</div>
|
|
12313
|
+
</div>`;
|
|
12314
|
+
if (def.steps.length > 0) {
|
|
12315
|
+
html += `<div class="wf-inspector-section">
|
|
12316
|
+
<div class="wf-inspector-section-title">Output Mapping</div>
|
|
12317
|
+
<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>
|
|
12318
|
+
</div>`;
|
|
12319
|
+
}
|
|
12320
|
+
html += `<div class="wf-inspector-section" style="margin-top:8px;">
|
|
12321
|
+
<div style="font-size:10px;color:var(--text-muted);">Add steps from the Palette tab, then drag between ports to connect them.</div>
|
|
12322
|
+
</div>`;
|
|
12323
|
+
} else {
|
|
12324
|
+
// Read-only: Description
|
|
12325
|
+
if (def.description) {
|
|
12326
|
+
html += `<div class="wf-inspector-section">
|
|
12327
|
+
<div class="wf-inspector-section-title">Description</div>
|
|
12328
|
+
<div style="font-size:12px;color:var(--text);line-height:1.4;">${escapeHtml(def.description)}</div>
|
|
10263
12329
|
</div>`;
|
|
10264
12330
|
}
|
|
10265
|
-
html += '</div>';
|
|
10266
|
-
}
|
|
10267
12331
|
|
|
10268
|
-
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
12332
|
+
// Read-only: Inputs
|
|
12333
|
+
if (def.inputs && Object.keys(def.inputs).length > 0) {
|
|
12334
|
+
html += '<div class="wf-inspector-section"><div class="wf-inspector-section-title">Inputs</div>';
|
|
12335
|
+
for (const [key, spec] of Object.entries(def.inputs)) {
|
|
12336
|
+
const req = spec.required ? ' <span style="color:#e74c3c">*</span>' : '';
|
|
12337
|
+
const defVal = spec.default !== undefined ? ` (default: ${spec.default})` : '';
|
|
12338
|
+
html += `<div style="margin-bottom:8px;">
|
|
12339
|
+
<div style="font-size:12px;font-weight:600;color:var(--text);">${escapeHtml(key)}${req}</div>
|
|
12340
|
+
<div style="font-size:11px;color:var(--text-muted);">${escapeHtml(spec.description || spec.type || '')}${defVal}</div>
|
|
12341
|
+
<input class="wf-inspector-input" id="wf-input-${key}" placeholder="${escapeHtml(key)}" value="${spec.default !== undefined ? spec.default : ''}">
|
|
12342
|
+
</div>`;
|
|
12343
|
+
}
|
|
12344
|
+
html += '</div>';
|
|
12345
|
+
}
|
|
10273
12346
|
|
|
10274
|
-
|
|
10275
|
-
if (def.output) {
|
|
12347
|
+
// Steps summary
|
|
10276
12348
|
html += `<div class="wf-inspector-section">
|
|
10277
|
-
<div class="wf-inspector-section-title">
|
|
10278
|
-
<div
|
|
12349
|
+
<div class="wf-inspector-section-title">Steps</div>
|
|
12350
|
+
<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
12351
|
</div>`;
|
|
12352
|
+
|
|
12353
|
+
// Output mapping
|
|
12354
|
+
if (def.output) {
|
|
12355
|
+
html += `<div class="wf-inspector-section">
|
|
12356
|
+
<div class="wf-inspector-section-title">Output</div>
|
|
12357
|
+
<div class="wf-inspector-code">${escapeHtml(JSON.stringify(def.output, null, 2))}</div>
|
|
12358
|
+
</div>`;
|
|
12359
|
+
}
|
|
10280
12360
|
}
|
|
10281
12361
|
|
|
10282
|
-
// Execution result
|
|
12362
|
+
// Execution result (shown in both modes)
|
|
10283
12363
|
if (wfState.executionResults._done) {
|
|
10284
12364
|
const r = wfState.executionResults._done;
|
|
10285
12365
|
const doneJson = JSON.stringify(r.output, null, 2);
|
|
@@ -10305,54 +12385,109 @@ function wfUpdateInspector() {
|
|
|
10305
12385
|
return;
|
|
10306
12386
|
}
|
|
10307
12387
|
|
|
10308
|
-
const meta = WF_NODE_META[step.tool] || { icon:
|
|
12388
|
+
const meta = WF_NODE_META[step.tool] || { icon: WF_FALLBACK_ICON, label: step.tool, color: '#666' };
|
|
10309
12389
|
header.textContent = step.name || step.id;
|
|
10310
12390
|
|
|
10311
12391
|
let html = '';
|
|
10312
12392
|
|
|
10313
|
-
// Tool badge
|
|
12393
|
+
// Tool badge (always shown)
|
|
10314
12394
|
html += `<div class="wf-inspector-section">
|
|
10315
12395
|
<div class="wf-inspector-section-title">Tool</div>
|
|
10316
|
-
<span class="wf-tool-badge" style="background:${meta.color}"
|
|
10317
|
-
</div>`;
|
|
10318
|
-
|
|
10319
|
-
// Step ID
|
|
10320
|
-
html += `<div class="wf-inspector-section">
|
|
10321
|
-
<div class="wf-inspector-section-title">ID</div>
|
|
10322
|
-
<div style="font-size:12px;color:var(--text);font-family:monospace;">${escapeHtml(step.id)}</div>
|
|
12396
|
+
<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>
|
|
10323
12397
|
</div>`;
|
|
10324
12398
|
|
|
10325
|
-
|
|
10326
|
-
|
|
12399
|
+
if (wfState.builderMode) {
|
|
12400
|
+
// Builder: editable step fields
|
|
12401
|
+
const sid = escapeHtml(step.id);
|
|
10327
12402
|
html += `<div class="wf-inspector-section">
|
|
10328
|
-
<div class="wf-inspector-section-title">
|
|
10329
|
-
|
|
10330
|
-
|
|
10331
|
-
|
|
10332
|
-
|
|
10333
|
-
|
|
10334
|
-
|
|
12403
|
+
<div class="wf-inspector-section-title">Step ID</div>
|
|
12404
|
+
<input class="wf-inspector-input" value="${sid}" style="font-family:monospace;" onchange="wfEditStepId('${sid}', this.value)">
|
|
12405
|
+
</div>`;
|
|
12406
|
+
html += `<div class="wf-inspector-section">
|
|
12407
|
+
<div class="wf-inspector-section-title">Name</div>
|
|
12408
|
+
<input class="wf-inspector-input" value="${escapeHtml(step.name || '')}" onchange="wfEditStepField('${sid}', 'name', this.value)">
|
|
12409
|
+
</div>`;
|
|
12410
|
+
|
|
12411
|
+
// Inputs from WF_INPUT_DEFS
|
|
12412
|
+
const inputDefs = WF_INPUT_DEFS[step.tool] || [];
|
|
12413
|
+
if (inputDefs.length > 0) {
|
|
12414
|
+
html += `<div class="wf-inspector-section"><div class="wf-inspector-section-title">Inputs</div>`;
|
|
12415
|
+
for (const d of inputDefs) {
|
|
12416
|
+
const val = step.inputs?.[d.key] ?? '';
|
|
12417
|
+
const display = typeof val === 'string' ? val : JSON.stringify(val);
|
|
12418
|
+
const req = d.required ? ' <span style="color:#e74c3c;font-size:10px;">required</span>' : '';
|
|
12419
|
+
html += `<div style="margin-bottom:8px;">
|
|
12420
|
+
<div style="font-size:11px;font-weight:600;color:var(--text);">${escapeHtml(d.key)}${req}</div>`;
|
|
12421
|
+
|
|
12422
|
+
if (d.type === 'textarea' || d.type === 'json') {
|
|
12423
|
+
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>`;
|
|
12424
|
+
} else if (d.type === 'select' && d.options) {
|
|
12425
|
+
html += `<select class="wf-inspector-input" onchange="wfEditStepInput('${sid}','${d.key}',this.value)">
|
|
12426
|
+
<option value="">--</option>`;
|
|
12427
|
+
for (const opt of d.options) {
|
|
12428
|
+
html += `<option value="${escapeHtml(opt)}" ${val === opt ? 'selected' : ''}>${escapeHtml(opt)}</option>`;
|
|
12429
|
+
}
|
|
12430
|
+
html += `</select>`;
|
|
12431
|
+
} else if (d.type === 'number') {
|
|
12432
|
+
html += `<input class="wf-inspector-input" type="number" value="${escapeHtml(String(val))}" placeholder="${escapeHtml(d.placeholder || '')}" onchange="wfEditStepInput('${sid}','${d.key}',this.value)">`;
|
|
12433
|
+
} else {
|
|
12434
|
+
html += `<input class="wf-inspector-input" value="${escapeHtml(display)}" placeholder="${escapeHtml(d.placeholder || '')}" onchange="wfEditStepInput('${sid}','${d.key}',this.value)">`;
|
|
12435
|
+
}
|
|
12436
|
+
html += `</div>`;
|
|
12437
|
+
}
|
|
12438
|
+
html += '</div>';
|
|
10335
12439
|
}
|
|
10336
|
-
html += '</div>';
|
|
10337
|
-
}
|
|
10338
12440
|
|
|
10339
|
-
|
|
10340
|
-
if (step.condition) {
|
|
12441
|
+
// Condition
|
|
10341
12442
|
html += `<div class="wf-inspector-section">
|
|
10342
|
-
<div class="wf-inspector-section-title">Condition
|
|
10343
|
-
<
|
|
12443
|
+
<div class="wf-inspector-section-title">Condition <span style="font-size:10px;color:var(--text-muted)">(optional)</span></div>
|
|
12444
|
+
<input class="wf-inspector-input" value="${escapeHtml(step.condition || '')}" placeholder="e.g. results.length > 0" onchange="wfEditStepField('${sid}', 'condition', this.value || undefined)">
|
|
12445
|
+
</div>`;
|
|
12446
|
+
|
|
12447
|
+
// continueOnError toggle
|
|
12448
|
+
html += `<div class="wf-inspector-section" style="display:flex;align-items:center;gap:8px;">
|
|
12449
|
+
<input type="checkbox" id="wf-coe-${sid}" ${step.continueOnError ? 'checked' : ''} onchange="wfEditStepField('${sid}', 'continueOnError', this.checked)">
|
|
12450
|
+
<label for="wf-coe-${sid}" style="font-size:11px;color:var(--text);cursor:pointer;">Continue on error</label>
|
|
10344
12451
|
</div>`;
|
|
10345
|
-
}
|
|
10346
12452
|
|
|
10347
|
-
|
|
10348
|
-
|
|
12453
|
+
// Delete button
|
|
12454
|
+
html += `<button class="wf-inspector-delete-btn" onclick="if(confirm('Delete step ${sid}?')) wfDeleteStep('${sid}')">Delete Step</button>`;
|
|
12455
|
+
} else {
|
|
12456
|
+
// Read-only step view
|
|
10349
12457
|
html += `<div class="wf-inspector-section">
|
|
10350
|
-
<div class="wf-inspector-section-title">
|
|
10351
|
-
<div
|
|
12458
|
+
<div class="wf-inspector-section-title">ID</div>
|
|
12459
|
+
<div style="font-size:12px;color:var(--text);font-family:monospace;">${escapeHtml(step.id)}</div>
|
|
10352
12460
|
</div>`;
|
|
12461
|
+
|
|
12462
|
+
if (step.inputs) {
|
|
12463
|
+
html += `<div class="wf-inspector-section">
|
|
12464
|
+
<div class="wf-inspector-section-title">Inputs</div>`;
|
|
12465
|
+
for (const [key, val] of Object.entries(step.inputs)) {
|
|
12466
|
+
const display = typeof val === 'string' ? val : JSON.stringify(val);
|
|
12467
|
+
html += `<div class="wf-inspector-field">
|
|
12468
|
+
<span class="wf-inspector-field-label">${escapeHtml(key)}</span>
|
|
12469
|
+
<span class="wf-inspector-field-value" style="font-family:monospace;font-size:11px;">${escapeHtml(display)}</span>
|
|
12470
|
+
</div>`;
|
|
12471
|
+
}
|
|
12472
|
+
html += '</div>';
|
|
12473
|
+
}
|
|
12474
|
+
|
|
12475
|
+
if (step.condition) {
|
|
12476
|
+
html += `<div class="wf-inspector-section">
|
|
12477
|
+
<div class="wf-inspector-section-title">Condition \u26A1</div>
|
|
12478
|
+
<div class="wf-inspector-code">${escapeHtml(step.condition)}</div>
|
|
12479
|
+
</div>`;
|
|
12480
|
+
}
|
|
12481
|
+
|
|
12482
|
+
if (step.forEach) {
|
|
12483
|
+
html += `<div class="wf-inspector-section">
|
|
12484
|
+
<div class="wf-inspector-section-title">ForEach</div>
|
|
12485
|
+
<div class="wf-inspector-code">${escapeHtml(JSON.stringify(step.forEach, null, 2))}</div>
|
|
12486
|
+
</div>`;
|
|
12487
|
+
}
|
|
10353
12488
|
}
|
|
10354
12489
|
|
|
10355
|
-
// Execution result for this step
|
|
12490
|
+
// Execution result for this step (shown in both modes)
|
|
10356
12491
|
const result = wfState.executionResults[step.id];
|
|
10357
12492
|
const state = wfState.executionState[step.id];
|
|
10358
12493
|
if (state === 'completed' && result) {
|
|
@@ -10416,6 +12551,7 @@ function wfSetToolbarEnabled(enabled) {
|
|
|
10416
12551
|
document.getElementById('wfRunBtn').disabled = !enabled;
|
|
10417
12552
|
document.getElementById('wfDryRunBtn').disabled = !enabled;
|
|
10418
12553
|
document.getElementById('wfExportBtn').disabled = !enabled;
|
|
12554
|
+
document.getElementById('wfEditBtn').disabled = !enabled;
|
|
10419
12555
|
}
|
|
10420
12556
|
|
|
10421
12557
|
// ── Export workflow JSON ──
|
|
@@ -10461,9 +12597,9 @@ function wfDryRun() {
|
|
|
10461
12597
|
const step = stepMap[stepId];
|
|
10462
12598
|
if (!step) return;
|
|
10463
12599
|
totalSteps++;
|
|
10464
|
-
const meta = WF_NODE_META[step.tool] || { icon:
|
|
12600
|
+
const meta = WF_NODE_META[step.tool] || { icon: WF_FALLBACK_ICON, label: step.tool, color: '#666' };
|
|
10465
12601
|
layersHtml += '<div class="wf-dryrun-step">';
|
|
10466
|
-
layersHtml += '<span class="wf-dryrun-step-icon"
|
|
12602
|
+
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
12603
|
layersHtml += '<div class="wf-dryrun-step-info">';
|
|
10468
12604
|
layersHtml += '<div class="wf-dryrun-step-name">' + escapeHtml(step.name || step.id) + '</div>';
|
|
10469
12605
|
layersHtml += '<div class="wf-dryrun-step-tool">' + escapeHtml(meta.label) + ' (' + escapeHtml(step.id) + ')</div>';
|
|
@@ -10601,27 +12737,128 @@ function wfStopExecution(reason) {
|
|
|
10601
12737
|
wfUpdateInspector();
|
|
10602
12738
|
}
|
|
10603
12739
|
|
|
10604
|
-
|
|
12740
|
+
// ── Input Modal (pre-execution) ──
|
|
12741
|
+
|
|
12742
|
+
function wfShowInputModal() {
|
|
12743
|
+
const def = wfState.activeWorkflow;
|
|
12744
|
+
if (!def || !def.inputs) return;
|
|
12745
|
+
const entries = Object.entries(def.inputs);
|
|
12746
|
+
if (entries.length === 0) { wfExecuteWithInputs({}); return; }
|
|
12747
|
+
|
|
12748
|
+
document.getElementById('wfInputModalTitle').textContent = (def.name || 'Workflow') + ' Inputs';
|
|
12749
|
+
let html = '';
|
|
12750
|
+
for (const [key, spec] of entries) {
|
|
12751
|
+
const req = spec.required ? ' <span style="color:#e74c3c">*</span>' : '';
|
|
12752
|
+
const desc = spec.description ? `<div class="wf-input-modal-desc">${escapeHtml(spec.description)}</div>` : '';
|
|
12753
|
+
// Pre-fill from inspector fields if available, then from defaults
|
|
12754
|
+
const inspectorEl = document.getElementById('wf-input-' + key);
|
|
12755
|
+
let prefill = inspectorEl ? inspectorEl.value : '';
|
|
12756
|
+
if (!prefill && spec.default !== undefined) prefill = String(spec.default);
|
|
12757
|
+
const placeholder = spec.type === 'number' ? 'number' : (spec.type || 'string');
|
|
12758
|
+
html += `<div class="wf-input-modal-field">
|
|
12759
|
+
<div class="wf-input-modal-label">${escapeHtml(key)}${req}</div>
|
|
12760
|
+
${desc}
|
|
12761
|
+
<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}">
|
|
12762
|
+
<div class="wf-input-modal-error" id="wf-modal-err-${key}">This field is required</div>
|
|
12763
|
+
</div>`;
|
|
12764
|
+
}
|
|
12765
|
+
document.getElementById('wfInputModalBody').innerHTML = html;
|
|
12766
|
+
document.getElementById('wfInputModalBackdrop').style.display = '';
|
|
12767
|
+
|
|
12768
|
+
// Focus first empty required field, or first field
|
|
12769
|
+
const firstEmpty = entries.find(([k, s]) => {
|
|
12770
|
+
const el = document.getElementById('wf-modal-input-' + k);
|
|
12771
|
+
return s.required && el && !el.value;
|
|
12772
|
+
});
|
|
12773
|
+
const focusKey = firstEmpty ? firstEmpty[0] : entries[0][0];
|
|
12774
|
+
const focusEl = document.getElementById('wf-modal-input-' + focusKey);
|
|
12775
|
+
if (focusEl) setTimeout(() => focusEl.focus(), 50);
|
|
12776
|
+
}
|
|
12777
|
+
|
|
12778
|
+
function wfCloseInputModal() {
|
|
12779
|
+
document.getElementById('wfInputModalBackdrop').style.display = 'none';
|
|
12780
|
+
}
|
|
12781
|
+
|
|
12782
|
+
function wfInputModalSubmit() {
|
|
12783
|
+
const def = wfState.activeWorkflow;
|
|
12784
|
+
if (!def || !def.inputs) return;
|
|
12785
|
+
|
|
12786
|
+
const inputs = {};
|
|
12787
|
+
let hasError = false;
|
|
12788
|
+
|
|
12789
|
+
for (const [key, spec] of Object.entries(def.inputs)) {
|
|
12790
|
+
const el = document.getElementById('wf-modal-input-' + key);
|
|
12791
|
+
const errEl = document.getElementById('wf-modal-err-' + key);
|
|
12792
|
+
if (!el) continue;
|
|
12793
|
+
|
|
12794
|
+
let val = el.value.trim();
|
|
12795
|
+
|
|
12796
|
+
// Clear previous error
|
|
12797
|
+
el.classList.remove('error');
|
|
12798
|
+
if (errEl) errEl.style.display = 'none';
|
|
12799
|
+
|
|
12800
|
+
// Required check
|
|
12801
|
+
if (!val && spec.required && spec.default === undefined) {
|
|
12802
|
+
el.classList.add('error');
|
|
12803
|
+
if (errEl) { errEl.textContent = 'This field is required'; errEl.style.display = ''; }
|
|
12804
|
+
hasError = true;
|
|
12805
|
+
continue;
|
|
12806
|
+
}
|
|
12807
|
+
|
|
12808
|
+
// Use default if empty
|
|
12809
|
+
if (!val && spec.default !== undefined) val = String(spec.default);
|
|
12810
|
+
|
|
12811
|
+
// Type validation
|
|
12812
|
+
if (spec.type === 'number' && val && isNaN(Number(val))) {
|
|
12813
|
+
el.classList.add('error');
|
|
12814
|
+
if (errEl) { errEl.textContent = 'Must be a number'; errEl.style.display = ''; }
|
|
12815
|
+
hasError = true;
|
|
12816
|
+
continue;
|
|
12817
|
+
}
|
|
12818
|
+
|
|
12819
|
+
// Coerce
|
|
12820
|
+
if (spec.type === 'number' && val) val = Number(val);
|
|
12821
|
+
if (val !== undefined && val !== '') inputs[key] = val;
|
|
12822
|
+
}
|
|
12823
|
+
|
|
12824
|
+
if (hasError) return;
|
|
12825
|
+
|
|
12826
|
+
// Also update inspector fields to keep them in sync
|
|
12827
|
+
for (const [key, val] of Object.entries(inputs)) {
|
|
12828
|
+
const inspEl = document.getElementById('wf-input-' + key);
|
|
12829
|
+
if (inspEl) inspEl.value = typeof val === 'number' ? String(val) : val;
|
|
12830
|
+
}
|
|
12831
|
+
|
|
12832
|
+
wfCloseInputModal();
|
|
12833
|
+
wfExecuteWithInputs(inputs);
|
|
12834
|
+
}
|
|
12835
|
+
|
|
12836
|
+
// Keyboard handler for input modal
|
|
12837
|
+
document.addEventListener('keydown', (e) => {
|
|
12838
|
+
const backdrop = document.getElementById('wfInputModalBackdrop');
|
|
12839
|
+
if (!backdrop || backdrop.style.display === 'none') return;
|
|
12840
|
+
if (e.key === 'Escape') { wfCloseInputModal(); e.preventDefault(); }
|
|
12841
|
+
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { wfInputModalSubmit(); e.preventDefault(); }
|
|
12842
|
+
});
|
|
12843
|
+
|
|
12844
|
+
async function wfExecute() {
|
|
12845
|
+
const def = wfState.activeWorkflow;
|
|
12846
|
+
if (!def || wfState.executing) return;
|
|
12847
|
+
|
|
12848
|
+
// If workflow has inputs, show the input modal instead of executing directly
|
|
12849
|
+
if (def.inputs && Object.keys(def.inputs).length > 0) {
|
|
12850
|
+
wfShowInputModal();
|
|
12851
|
+
return;
|
|
12852
|
+
}
|
|
12853
|
+
|
|
12854
|
+
// No inputs needed, execute directly
|
|
12855
|
+
wfExecuteWithInputs({});
|
|
12856
|
+
}
|
|
12857
|
+
|
|
12858
|
+
async function wfExecuteWithInputs(inputs) {
|
|
10605
12859
|
const def = wfState.activeWorkflow;
|
|
10606
12860
|
if (!def || wfState.executing) return;
|
|
10607
12861
|
|
|
10608
|
-
// Collect inputs
|
|
10609
|
-
const inputs = {};
|
|
10610
|
-
if (def.inputs) {
|
|
10611
|
-
for (const [key, spec] of Object.entries(def.inputs)) {
|
|
10612
|
-
const el = document.getElementById('wf-input-' + key);
|
|
10613
|
-
let val = el ? el.value : (spec.default !== undefined ? spec.default : undefined);
|
|
10614
|
-
if (val === '' && spec.default !== undefined) val = spec.default;
|
|
10615
|
-
if (val === '' && spec.required) {
|
|
10616
|
-
alert('Input "' + key + '" is required');
|
|
10617
|
-
return;
|
|
10618
|
-
}
|
|
10619
|
-
// Type coerce
|
|
10620
|
-
if (spec.type === 'number' && val !== undefined && val !== '') val = Number(val);
|
|
10621
|
-
if (val !== undefined && val !== '') inputs[key] = val;
|
|
10622
|
-
}
|
|
10623
|
-
}
|
|
10624
|
-
|
|
10625
12862
|
wfState.executing = true;
|
|
10626
12863
|
wfState.executionResults = {};
|
|
10627
12864
|
wfAbortController = new AbortController();
|
|
@@ -10907,7 +13144,7 @@ function wfInitPan() {
|
|
|
10907
13144
|
// Scroll wheel zoom, centered on cursor position
|
|
10908
13145
|
svg.addEventListener('wheel', (e) => {
|
|
10909
13146
|
e.preventDefault();
|
|
10910
|
-
const factor = e.deltaY < 0 ? 1.
|
|
13147
|
+
const factor = e.deltaY < 0 ? 1.03 : 1 / 1.03;
|
|
10911
13148
|
const oldZoom = wfState.zoom;
|
|
10912
13149
|
const newZoom = Math.max(0.2, Math.min(5, oldZoom * factor));
|
|
10913
13150
|
// Zoom toward cursor: keep the SVG point under the mouse fixed
|
|
@@ -10928,6 +13165,452 @@ function wfInitPan() {
|
|
|
10928
13165
|
});
|
|
10929
13166
|
}
|
|
10930
13167
|
|
|
13168
|
+
// ── Builder: Input Definitions ──
|
|
13169
|
+
const WF_INPUT_DEFS = {
|
|
13170
|
+
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: '{}' }],
|
|
13171
|
+
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: '{}' }],
|
|
13172
|
+
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' }],
|
|
13173
|
+
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'] }],
|
|
13174
|
+
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'] }],
|
|
13175
|
+
similarity: [{ key: 'text1', type: 'text', required: true }, { key: 'text2', type: 'text', required: true }, { key: 'model', type: 'text', required: false }],
|
|
13176
|
+
collections: [{ key: 'db', type: 'text', required: false }],
|
|
13177
|
+
models: [{ key: 'category', type: 'select', required: false, options: ['embedding','rerank','all'] }],
|
|
13178
|
+
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' }],
|
|
13179
|
+
explain: [{ key: 'topic', type: 'text', required: true }],
|
|
13180
|
+
topics: [{ key: 'search', type: 'text', required: false }],
|
|
13181
|
+
merge: [{ key: 'sources', type: 'json', required: true, placeholder: '["step1.output","step2.output"]' }, { key: 'strategy', type: 'select', required: false, options: ['concat','interleave','unique'] }],
|
|
13182
|
+
filter: [{ key: 'input', type: 'text', required: true, placeholder: '{{ step.output }}' }, { key: 'condition', type: 'text', required: true, placeholder: 'item.score > 0.5' }],
|
|
13183
|
+
transform: [{ key: 'input', type: 'text', required: true, placeholder: '{{ step.output }}' }, { key: 'expression', type: 'text', required: true, placeholder: 'item.text' }],
|
|
13184
|
+
generate: [{ key: 'prompt', type: 'textarea', required: true, placeholder: 'Generate a summary of...' }, { key: 'context', type: 'text', required: false, placeholder: '{{ step.output }}' }],
|
|
13185
|
+
conditional: [{ key: 'condition', type: 'text', required: true, placeholder: '{{ step.output.results.length > 0 }}' }, { key: 'then', type: 'json', required: true, placeholder: '["step_a"]' }, { key: 'else', type: 'json', required: false, placeholder: '["step_b"]' }],
|
|
13186
|
+
loop: [{ key: 'items', type: 'text', required: true, placeholder: '{{ step.output.results }}' }, { key: 'as', type: 'text', required: true, placeholder: 'item' }, { key: 'step', type: 'json', required: true, placeholder: '{"tool":"template","inputs":{"text":"{{ item }}"}}' }, { key: 'maxIterations', type: 'number', required: false, placeholder: '100' }],
|
|
13187
|
+
template: [{ key: 'text', type: 'textarea', required: true, placeholder: 'Compose text with {{ step.output }} references' }],
|
|
13188
|
+
chunk: [{ key: 'text', type: 'textarea', required: true, placeholder: '{{ step.output.text }}' }, { key: 'strategy', type: 'select', required: false, options: ['fixed','sentence','paragraph','recursive','markdown'] }, { key: 'size', type: 'number', required: false, placeholder: '512' }, { key: 'overlap', type: 'number', required: false, placeholder: '50' }, { key: 'source', type: 'text', required: false, placeholder: 'document.md' }],
|
|
13189
|
+
aggregate: [{ key: 'pipeline', type: 'json', required: true, placeholder: '[{"$group":{"_id":"$field","count":{"$sum":1}}}]' }, { key: 'collection', type: 'text', required: false }, { key: 'db', type: 'text', required: false }],
|
|
13190
|
+
http: [{ key: 'url', type: 'text', required: true, placeholder: 'https://api.example.com/data' }, { key: 'method', type: 'select', required: false, options: ['GET','POST','PUT','PATCH','DELETE'] }, { key: 'headers', type: 'json', required: false, placeholder: '{"Authorization":"Bearer ..."}' }, { key: 'body', type: 'json', required: false, placeholder: '{}' }, { key: 'timeout', type: 'number', required: false, placeholder: '30000' }],
|
|
13191
|
+
};
|
|
13192
|
+
|
|
13193
|
+
const WF_CATEGORY_ORDER = ['retrieval', 'embedding', 'processing', 'control', 'generation', 'integration', 'management', 'utility'];
|
|
13194
|
+
const WF_CATEGORY_LABELS = { retrieval: 'Retrieval', embedding: 'Embedding', management: 'Management', utility: 'Utility', control: 'Control Flow', generation: 'Generation', processing: 'Processing', integration: 'Integration' };
|
|
13195
|
+
|
|
13196
|
+
// ── Builder: Library/Palette tab toggle ──
|
|
13197
|
+
function wfSwitchLibTab(tab) {
|
|
13198
|
+
const libraryList = document.getElementById('wfLibraryList');
|
|
13199
|
+
const paletteList = document.getElementById('wfPaletteList');
|
|
13200
|
+
document.querySelectorAll('.wf-lib-tab').forEach(b => b.classList.toggle('active', b.dataset.libTab === tab));
|
|
13201
|
+
if (libraryList) libraryList.style.display = tab === 'library' ? '' : 'none';
|
|
13202
|
+
if (paletteList) paletteList.style.display = tab === 'palette' ? '' : 'none';
|
|
13203
|
+
if (tab === 'palette') wfRenderPalette();
|
|
13204
|
+
}
|
|
13205
|
+
|
|
13206
|
+
// ── Builder: Palette rendering ──
|
|
13207
|
+
function wfRenderPalette() {
|
|
13208
|
+
const container = document.getElementById('wfPaletteList');
|
|
13209
|
+
if (!container) return;
|
|
13210
|
+
const grouped = {};
|
|
13211
|
+
for (const [tool, meta] of Object.entries(WF_NODE_META)) {
|
|
13212
|
+
const cat = meta.category || 'unknown';
|
|
13213
|
+
if (!grouped[cat]) grouped[cat] = [];
|
|
13214
|
+
grouped[cat].push({ tool, ...meta });
|
|
13215
|
+
}
|
|
13216
|
+
let html = '';
|
|
13217
|
+
for (const cat of WF_CATEGORY_ORDER) {
|
|
13218
|
+
const items = grouped[cat];
|
|
13219
|
+
if (!items) continue;
|
|
13220
|
+
html += `<div class="wf-palette-category"><div class="wf-palette-category-title">${WF_CATEGORY_LABELS[cat] || cat}</div>`;
|
|
13221
|
+
for (const item of items) {
|
|
13222
|
+
html += `<div class="wf-palette-item" draggable="true" ondragstart="event.dataTransfer.setData('text/plain','${item.tool}')" onclick="wfAddNodeFromPalette('${item.tool}')">
|
|
13223
|
+
<span class="wf-palette-icon" style="color:${item.color}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="${item.icon || WF_FALLBACK_ICON}"/></svg></span>
|
|
13224
|
+
<span class="wf-palette-label">${item.label}</span>
|
|
13225
|
+
</div>`;
|
|
13226
|
+
}
|
|
13227
|
+
html += '</div>';
|
|
13228
|
+
}
|
|
13229
|
+
container.innerHTML = html;
|
|
13230
|
+
}
|
|
13231
|
+
|
|
13232
|
+
// ── Builder: New Workflow ──
|
|
13233
|
+
function wfNewWorkflow() {
|
|
13234
|
+
wfState.activeWorkflow = {
|
|
13235
|
+
name: 'Untitled Workflow',
|
|
13236
|
+
description: '',
|
|
13237
|
+
steps: [],
|
|
13238
|
+
inputs: {},
|
|
13239
|
+
defaults: {},
|
|
13240
|
+
output: {}
|
|
13241
|
+
};
|
|
13242
|
+
wfState.builderMode = true;
|
|
13243
|
+
wfState.dirtyFlag = false;
|
|
13244
|
+
wfState.selectedNodeId = null;
|
|
13245
|
+
wfState.executionState = {};
|
|
13246
|
+
wfState.executionResults = {};
|
|
13247
|
+
wfState.nodePositions = {};
|
|
13248
|
+
wfState.layers = [];
|
|
13249
|
+
wfState.graph = {};
|
|
13250
|
+
wfState.zoom = 1;
|
|
13251
|
+
wfState.panX = 0;
|
|
13252
|
+
wfState.panY = 0;
|
|
13253
|
+
wfState.draggingEdge = null;
|
|
13254
|
+
wfState.dragNode = null;
|
|
13255
|
+
|
|
13256
|
+
// Clear canvas
|
|
13257
|
+
const svg = document.getElementById('wf-canvas');
|
|
13258
|
+
if (svg) svg.querySelectorAll('.wf-node, .wf-edge-group').forEach(el => el.remove());
|
|
13259
|
+
document.getElementById('wfCanvasEmpty').style.display = 'none';
|
|
13260
|
+
svg.style.display = '';
|
|
13261
|
+
|
|
13262
|
+
// Enable toolbar buttons
|
|
13263
|
+
document.getElementById('wfDryRunBtn').disabled = false;
|
|
13264
|
+
document.getElementById('wfRunBtn').disabled = false;
|
|
13265
|
+
document.getElementById('wfExportBtn').disabled = false;
|
|
13266
|
+
|
|
13267
|
+
// Switch to palette tab
|
|
13268
|
+
wfSwitchLibTab('palette');
|
|
13269
|
+
|
|
13270
|
+
// Open inspector for workflow-level editing
|
|
13271
|
+
wfOpenInspector();
|
|
13272
|
+
wfUpdateInspector();
|
|
13273
|
+
|
|
13274
|
+
// Set viewBox
|
|
13275
|
+
wfApplyViewBox();
|
|
13276
|
+
}
|
|
13277
|
+
|
|
13278
|
+
// ── Builder: Edit current workflow ──
|
|
13279
|
+
function wfEditWorkflow() {
|
|
13280
|
+
if (!wfState.activeWorkflow) return;
|
|
13281
|
+
wfState.builderMode = true;
|
|
13282
|
+
wfState.dirtyFlag = false;
|
|
13283
|
+
wfState.selectedNodeId = null;
|
|
13284
|
+
wfState.draggingEdge = null;
|
|
13285
|
+
wfState.dragNode = null;
|
|
13286
|
+
|
|
13287
|
+
// Re-render nodes with builder ports and drag handles
|
|
13288
|
+
wfRefreshNodes();
|
|
13289
|
+
|
|
13290
|
+
// Switch to palette tab and open inspector for workflow-level editing
|
|
13291
|
+
wfSwitchLibTab('palette');
|
|
13292
|
+
wfOpenInspector();
|
|
13293
|
+
wfUpdateInspector();
|
|
13294
|
+
}
|
|
13295
|
+
|
|
13296
|
+
// ── Builder: Add node from palette ──
|
|
13297
|
+
function wfAddNodeFromPalette(tool) {
|
|
13298
|
+
if (!wfState.activeWorkflow) wfNewWorkflow();
|
|
13299
|
+
wfState.builderMode = true;
|
|
13300
|
+
const def = wfState.activeWorkflow;
|
|
13301
|
+
|
|
13302
|
+
// Generate unique step ID
|
|
13303
|
+
let baseId = tool;
|
|
13304
|
+
let id = baseId;
|
|
13305
|
+
let counter = 2;
|
|
13306
|
+
const existingIds = new Set(def.steps.map(s => s.id));
|
|
13307
|
+
while (existingIds.has(id)) { id = baseId + '_' + counter; counter++; }
|
|
13308
|
+
|
|
13309
|
+
// Build default inputs
|
|
13310
|
+
const inputDefs = WF_INPUT_DEFS[tool] || [];
|
|
13311
|
+
const inputs = {};
|
|
13312
|
+
for (const d of inputDefs) {
|
|
13313
|
+
if (d.required) inputs[d.key] = '';
|
|
13314
|
+
}
|
|
13315
|
+
|
|
13316
|
+
const meta = WF_NODE_META[tool] || {};
|
|
13317
|
+
const step = {
|
|
13318
|
+
id,
|
|
13319
|
+
name: meta.label || tool,
|
|
13320
|
+
tool,
|
|
13321
|
+
inputs,
|
|
13322
|
+
};
|
|
13323
|
+
def.steps.push(step);
|
|
13324
|
+
|
|
13325
|
+
// Position: place to the right of all existing nodes
|
|
13326
|
+
let maxX = WF_PAD;
|
|
13327
|
+
for (const pos of Object.values(wfState.nodePositions)) {
|
|
13328
|
+
if (pos.x + WF_NODE_W + WF_LAYER_GAP > maxX) maxX = pos.x + WF_NODE_W + WF_LAYER_GAP;
|
|
13329
|
+
}
|
|
13330
|
+
let y = WF_PAD;
|
|
13331
|
+
// Stack vertically if there are nodes in the same column
|
|
13332
|
+
const nodesAtX = Object.values(wfState.nodePositions).filter(p => Math.abs(p.x - maxX) < 20);
|
|
13333
|
+
if (nodesAtX.length > 0) {
|
|
13334
|
+
y = Math.max(...nodesAtX.map(p => p.y)) + WF_NODE_H + WF_NODE_GAP;
|
|
13335
|
+
}
|
|
13336
|
+
wfState.nodePositions[id] = { x: maxX, y };
|
|
13337
|
+
|
|
13338
|
+
// Rebuild graph
|
|
13339
|
+
wfBuildGraph();
|
|
13340
|
+
wfRefreshNodes();
|
|
13341
|
+
wfSelectNode(id);
|
|
13342
|
+
wfState.dirtyFlag = true;
|
|
13343
|
+
}
|
|
13344
|
+
|
|
13345
|
+
// ── Builder: Build graph from step inputs (template references) ──
|
|
13346
|
+
function wfBuildGraph() {
|
|
13347
|
+
const def = wfState.activeWorkflow;
|
|
13348
|
+
if (!def) return;
|
|
13349
|
+
const graph = {};
|
|
13350
|
+
const stepIds = new Set(def.steps.map(s => s.id));
|
|
13351
|
+
for (const step of def.steps) {
|
|
13352
|
+
const deps = new Set();
|
|
13353
|
+
// Scan all input values for {{ stepId.xxx }} references
|
|
13354
|
+
for (const val of Object.values(step.inputs || {})) {
|
|
13355
|
+
const str = typeof val === 'string' ? val : JSON.stringify(val);
|
|
13356
|
+
const matches = str.matchAll(/\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\./g);
|
|
13357
|
+
for (const m of matches) {
|
|
13358
|
+
if (stepIds.has(m[1]) && m[1] !== step.id) deps.add(m[1]);
|
|
13359
|
+
}
|
|
13360
|
+
}
|
|
13361
|
+
graph[step.id] = Array.from(deps);
|
|
13362
|
+
}
|
|
13363
|
+
wfState.graph = graph;
|
|
13364
|
+
}
|
|
13365
|
+
|
|
13366
|
+
// ── Builder: Canvas drop (from palette drag) ──
|
|
13367
|
+
function wfCanvasDrop(e) {
|
|
13368
|
+
e.preventDefault();
|
|
13369
|
+
const tool = e.dataTransfer.getData('text/plain');
|
|
13370
|
+
if (!tool || !WF_NODE_META[tool]) return;
|
|
13371
|
+
if (!wfState.activeWorkflow) wfNewWorkflow();
|
|
13372
|
+
wfState.builderMode = true;
|
|
13373
|
+
const def = wfState.activeWorkflow;
|
|
13374
|
+
|
|
13375
|
+
// Generate unique ID
|
|
13376
|
+
let id = tool;
|
|
13377
|
+
let counter = 2;
|
|
13378
|
+
const existingIds = new Set(def.steps.map(s => s.id));
|
|
13379
|
+
while (existingIds.has(id)) { id = tool + '_' + counter; counter++; }
|
|
13380
|
+
|
|
13381
|
+
// Inputs
|
|
13382
|
+
const inputDefs = WF_INPUT_DEFS[tool] || [];
|
|
13383
|
+
const inputs = {};
|
|
13384
|
+
for (const d of inputDefs) { if (d.required) inputs[d.key] = ''; }
|
|
13385
|
+
|
|
13386
|
+
const meta = WF_NODE_META[tool] || {};
|
|
13387
|
+
const step = { id, name: meta.label || tool, tool, inputs };
|
|
13388
|
+
def.steps.push(step);
|
|
13389
|
+
|
|
13390
|
+
// Convert drop coordinates to SVG space
|
|
13391
|
+
const svg = document.getElementById('wf-canvas');
|
|
13392
|
+
const rect = svg.getBoundingClientRect();
|
|
13393
|
+
const svgX = (e.clientX - rect.left) / wfState.zoom + wfState.panX;
|
|
13394
|
+
const svgY = (e.clientY - rect.top) / wfState.zoom + wfState.panY;
|
|
13395
|
+
wfState.nodePositions[id] = { x: svgX, y: svgY };
|
|
13396
|
+
|
|
13397
|
+
wfBuildGraph();
|
|
13398
|
+
wfRefreshNodes();
|
|
13399
|
+
wfSelectNode(id);
|
|
13400
|
+
wfState.dirtyFlag = true;
|
|
13401
|
+
}
|
|
13402
|
+
|
|
13403
|
+
// ── Builder: Mutation helpers ──
|
|
13404
|
+
function wfEditStepField(stepId, field, value) {
|
|
13405
|
+
const step = wfState.activeWorkflow?.steps.find(s => s.id === stepId);
|
|
13406
|
+
if (!step) return;
|
|
13407
|
+
step[field] = value;
|
|
13408
|
+
wfState.dirtyFlag = true;
|
|
13409
|
+
if (field === 'name') wfRefreshNodes();
|
|
13410
|
+
}
|
|
13411
|
+
|
|
13412
|
+
function wfEditStepInput(stepId, key, value) {
|
|
13413
|
+
const step = wfState.activeWorkflow?.steps.find(s => s.id === stepId);
|
|
13414
|
+
if (!step) return;
|
|
13415
|
+
if (!step.inputs) step.inputs = {};
|
|
13416
|
+
step.inputs[key] = value;
|
|
13417
|
+
wfState.dirtyFlag = true;
|
|
13418
|
+
// Rebuild graph in case template refs changed
|
|
13419
|
+
wfBuildGraph();
|
|
13420
|
+
wfRefreshNodes();
|
|
13421
|
+
}
|
|
13422
|
+
|
|
13423
|
+
function wfEditStepId(oldId, newId) {
|
|
13424
|
+
const def = wfState.activeWorkflow;
|
|
13425
|
+
if (!def) return;
|
|
13426
|
+
newId = newId.trim().replace(/[^a-zA-Z0-9_]/g, '_');
|
|
13427
|
+
if (!newId || newId === oldId) return;
|
|
13428
|
+
if (def.steps.some(s => s.id === newId)) return; // duplicate
|
|
13429
|
+
|
|
13430
|
+
const step = def.steps.find(s => s.id === oldId);
|
|
13431
|
+
if (!step) return;
|
|
13432
|
+
step.id = newId;
|
|
13433
|
+
|
|
13434
|
+
// Update position map
|
|
13435
|
+
if (wfState.nodePositions[oldId]) {
|
|
13436
|
+
wfState.nodePositions[newId] = wfState.nodePositions[oldId];
|
|
13437
|
+
delete wfState.nodePositions[oldId];
|
|
13438
|
+
}
|
|
13439
|
+
// Update template references in other steps
|
|
13440
|
+
for (const s of def.steps) {
|
|
13441
|
+
for (const [k, v] of Object.entries(s.inputs || {})) {
|
|
13442
|
+
if (typeof v === 'string' && v.includes('{{ ' + oldId + '.')) {
|
|
13443
|
+
s.inputs[k] = v.replaceAll('{{ ' + oldId + '.', '{{ ' + newId + '.');
|
|
13444
|
+
}
|
|
13445
|
+
}
|
|
13446
|
+
}
|
|
13447
|
+
if (wfState.selectedNodeId === oldId) wfState.selectedNodeId = newId;
|
|
13448
|
+
wfState.dirtyFlag = true;
|
|
13449
|
+
wfBuildGraph();
|
|
13450
|
+
wfRefreshNodes();
|
|
13451
|
+
wfUpdateInspector();
|
|
13452
|
+
}
|
|
13453
|
+
|
|
13454
|
+
function wfDeleteStep(stepId) {
|
|
13455
|
+
const def = wfState.activeWorkflow;
|
|
13456
|
+
if (!def) return;
|
|
13457
|
+
def.steps = def.steps.filter(s => s.id !== stepId);
|
|
13458
|
+
delete wfState.nodePositions[stepId];
|
|
13459
|
+
if (wfState.selectedNodeId === stepId) wfState.selectedNodeId = null;
|
|
13460
|
+
wfState.dirtyFlag = true;
|
|
13461
|
+
wfBuildGraph();
|
|
13462
|
+
wfRefreshNodes();
|
|
13463
|
+
wfUpdateInspector();
|
|
13464
|
+
}
|
|
13465
|
+
|
|
13466
|
+
function wfEditWorkflowField(field, value) {
|
|
13467
|
+
if (!wfState.activeWorkflow) return;
|
|
13468
|
+
wfState.activeWorkflow[field] = value;
|
|
13469
|
+
wfState.dirtyFlag = true;
|
|
13470
|
+
}
|
|
13471
|
+
|
|
13472
|
+
// ── Builder: Validate ──
|
|
13473
|
+
async function wfValidateBuilder() {
|
|
13474
|
+
const def = wfState.activeWorkflow;
|
|
13475
|
+
if (!def) return null;
|
|
13476
|
+
try {
|
|
13477
|
+
const res = await fetch('/api/workflows/validate', {
|
|
13478
|
+
method: 'POST',
|
|
13479
|
+
headers: { 'Content-Type': 'application/json' },
|
|
13480
|
+
body: JSON.stringify(def),
|
|
13481
|
+
});
|
|
13482
|
+
return await res.json();
|
|
13483
|
+
} catch (err) {
|
|
13484
|
+
return { valid: false, errors: [err.message] };
|
|
13485
|
+
}
|
|
13486
|
+
}
|
|
13487
|
+
|
|
13488
|
+
// ── Builder: Edge drag ──
|
|
13489
|
+
function wfEdgeDragStart(fromId, fromX, fromY) {
|
|
13490
|
+
const svg = document.getElementById('wf-canvas');
|
|
13491
|
+
if (!svg) return;
|
|
13492
|
+
wfState.draggingEdge = { fromId, fromX, fromY };
|
|
13493
|
+
|
|
13494
|
+
// Create temp edge (dashed bezier), pointer-events: none so it doesn't block port hit-testing
|
|
13495
|
+
const tempPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
13496
|
+
tempPath.setAttribute('id', 'wf-temp-edge');
|
|
13497
|
+
tempPath.setAttribute('fill', 'none');
|
|
13498
|
+
tempPath.setAttribute('stroke', 'var(--accent)');
|
|
13499
|
+
tempPath.setAttribute('stroke-width', '2');
|
|
13500
|
+
tempPath.setAttribute('stroke-dasharray', '6 4');
|
|
13501
|
+
tempPath.setAttribute('pointer-events', 'none');
|
|
13502
|
+
tempPath.setAttribute('d', `M${fromX},${fromY} L${fromX},${fromY}`);
|
|
13503
|
+
svg.appendChild(tempPath);
|
|
13504
|
+
|
|
13505
|
+
function onMove(e) {
|
|
13506
|
+
const rect = svg.getBoundingClientRect();
|
|
13507
|
+
const mx = (e.clientX - rect.left) / wfState.zoom + wfState.panX;
|
|
13508
|
+
const my = (e.clientY - rect.top) / wfState.zoom + wfState.panY;
|
|
13509
|
+
const dx = Math.abs(mx - fromX) * 0.5;
|
|
13510
|
+
tempPath.setAttribute('d', `M${fromX},${fromY} C${fromX + dx},${fromY} ${mx - dx},${my} ${mx},${my}`);
|
|
13511
|
+
}
|
|
13512
|
+
|
|
13513
|
+
function onUp() {
|
|
13514
|
+
document.removeEventListener('mousemove', onMove);
|
|
13515
|
+
document.removeEventListener('mouseup', onUp);
|
|
13516
|
+
const el = document.getElementById('wf-temp-edge');
|
|
13517
|
+
if (el) el.remove();
|
|
13518
|
+
wfState.draggingEdge = null;
|
|
13519
|
+
}
|
|
13520
|
+
|
|
13521
|
+
document.addEventListener('mousemove', onMove);
|
|
13522
|
+
document.addEventListener('mouseup', onUp);
|
|
13523
|
+
}
|
|
13524
|
+
|
|
13525
|
+
function wfEdgeDropOnInput(toId) {
|
|
13526
|
+
if (!wfState.draggingEdge) return;
|
|
13527
|
+
const fromId = wfState.draggingEdge.fromId;
|
|
13528
|
+
if (fromId === toId) return; // no self-connections
|
|
13529
|
+
|
|
13530
|
+
// Add template reference to the target step's first empty required input
|
|
13531
|
+
const def = wfState.activeWorkflow;
|
|
13532
|
+
if (!def) return;
|
|
13533
|
+
const targetStep = def.steps.find(s => s.id === toId);
|
|
13534
|
+
if (!targetStep) return;
|
|
13535
|
+
|
|
13536
|
+
const inputDefs = WF_INPUT_DEFS[targetStep.tool] || [];
|
|
13537
|
+
let connected = false;
|
|
13538
|
+
|
|
13539
|
+
// Try to fill the first empty required input with a template reference
|
|
13540
|
+
for (const d of inputDefs) {
|
|
13541
|
+
if (!targetStep.inputs) targetStep.inputs = {};
|
|
13542
|
+
const current = targetStep.inputs[d.key];
|
|
13543
|
+
if (!current || current === '') {
|
|
13544
|
+
targetStep.inputs[d.key] = `{{ ${fromId}.output }}`;
|
|
13545
|
+
connected = true;
|
|
13546
|
+
break;
|
|
13547
|
+
}
|
|
13548
|
+
}
|
|
13549
|
+
|
|
13550
|
+
// If no empty required input, try first empty optional input
|
|
13551
|
+
if (!connected) {
|
|
13552
|
+
for (const d of inputDefs) {
|
|
13553
|
+
const current = targetStep.inputs?.[d.key];
|
|
13554
|
+
if (!current || current === '') {
|
|
13555
|
+
if (!targetStep.inputs) targetStep.inputs = {};
|
|
13556
|
+
targetStep.inputs[d.key] = `{{ ${fromId}.output }}`;
|
|
13557
|
+
connected = true;
|
|
13558
|
+
break;
|
|
13559
|
+
}
|
|
13560
|
+
}
|
|
13561
|
+
}
|
|
13562
|
+
|
|
13563
|
+
if (connected) {
|
|
13564
|
+
wfState.dirtyFlag = true;
|
|
13565
|
+
wfBuildGraph();
|
|
13566
|
+
wfRelayout();
|
|
13567
|
+
wfRefreshNodes();
|
|
13568
|
+
if (wfState.selectedNodeId === toId) wfUpdateInspector();
|
|
13569
|
+
}
|
|
13570
|
+
|
|
13571
|
+
// Clean up drag state
|
|
13572
|
+
wfState.draggingEdge = null;
|
|
13573
|
+
const el = document.getElementById('wf-temp-edge');
|
|
13574
|
+
if (el) el.remove();
|
|
13575
|
+
}
|
|
13576
|
+
|
|
13577
|
+
// ── Builder: Relayout via topological sort ──
|
|
13578
|
+
async function wfRelayout() {
|
|
13579
|
+
const def = wfState.activeWorkflow;
|
|
13580
|
+
if (!def || def.steps.length === 0) return;
|
|
13581
|
+
|
|
13582
|
+
try {
|
|
13583
|
+
const res = await fetch('/api/workflows/plan', {
|
|
13584
|
+
method: 'POST',
|
|
13585
|
+
headers: { 'Content-Type': 'application/json' },
|
|
13586
|
+
body: JSON.stringify(def),
|
|
13587
|
+
});
|
|
13588
|
+
const data = await res.json();
|
|
13589
|
+
if (data.layers && data.layers.length > 0) {
|
|
13590
|
+
wfState.layers = data.layers;
|
|
13591
|
+
// Reposition nodes based on layers
|
|
13592
|
+
const positions = {};
|
|
13593
|
+
data.layers.forEach((layer, li) => {
|
|
13594
|
+
layer.forEach((stepId, ni) => {
|
|
13595
|
+
positions[stepId] = {
|
|
13596
|
+
x: WF_PAD + li * WF_LAYER_GAP,
|
|
13597
|
+
y: WF_PAD + ni * (WF_NODE_H + WF_NODE_GAP),
|
|
13598
|
+
};
|
|
13599
|
+
});
|
|
13600
|
+
});
|
|
13601
|
+
// Keep orphan nodes (not in any layer) at their current position
|
|
13602
|
+
for (const step of def.steps) {
|
|
13603
|
+
if (!positions[step.id] && wfState.nodePositions[step.id]) {
|
|
13604
|
+
positions[step.id] = wfState.nodePositions[step.id];
|
|
13605
|
+
}
|
|
13606
|
+
}
|
|
13607
|
+
wfState.nodePositions = positions;
|
|
13608
|
+
}
|
|
13609
|
+
} catch (err) {
|
|
13610
|
+
console.warn('Relayout failed:', err.message);
|
|
13611
|
+
}
|
|
13612
|
+
}
|
|
13613
|
+
|
|
10931
13614
|
// ── Docs shortcut (F1) ──
|
|
10932
13615
|
const DOCS_URLS = {
|
|
10933
13616
|
embed: 'https://docs.vaicli.com/docs/commands/embeddings/embed',
|
|
@@ -10956,6 +13639,22 @@ document.addEventListener('keydown', (e) => {
|
|
|
10956
13639
|
document.addEventListener('keydown', (e) => {
|
|
10957
13640
|
const activeTab = document.querySelector('.tab-btn.active');
|
|
10958
13641
|
if (!activeTab || activeTab.dataset.tab !== 'workflows') return;
|
|
13642
|
+
|
|
13643
|
+
// Ctrl/Cmd+S to save/export in builder mode (works even in inputs)
|
|
13644
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 's' && wfState.builderMode) {
|
|
13645
|
+
e.preventDefault();
|
|
13646
|
+
wfExportJson();
|
|
13647
|
+
return;
|
|
13648
|
+
}
|
|
13649
|
+
|
|
13650
|
+
// Delete/Backspace to remove selected node in builder mode
|
|
13651
|
+
if ((e.key === 'Delete' || e.key === 'Backspace') && wfState.builderMode && wfState.selectedNodeId) {
|
|
13652
|
+
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
|
13653
|
+
e.preventDefault();
|
|
13654
|
+
wfDeleteStep(wfState.selectedNodeId);
|
|
13655
|
+
return;
|
|
13656
|
+
}
|
|
13657
|
+
|
|
10959
13658
|
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
|
10960
13659
|
|
|
10961
13660
|
const PAN_STEP = 40;
|
|
@@ -10970,6 +13669,382 @@ document.addEventListener('keydown', (e) => {
|
|
|
10970
13669
|
});
|
|
10971
13670
|
|
|
10972
13671
|
// ── Init ──
|
|
13672
|
+
// ── Workflow Store ──
|
|
13673
|
+
const WF_STORE_TOOL_COLORS = {
|
|
13674
|
+
query:'#00D4AA',search:'#40E0FF',rerank:'#0EA5E9',embed:'#8B5CF6',similarity:'#A78BFA',
|
|
13675
|
+
ingest:'#10B981',collections:'#F59E0B',models:'#EF4444',explain:'#EC4899',estimate:'#F97316',
|
|
13676
|
+
merge:'#6366F1',filter:'#14B8A6',transform:'#D946EF',generate:'#F472B6'
|
|
13677
|
+
};
|
|
13678
|
+
const WF_STORE_CATEGORIES = [
|
|
13679
|
+
{ id:'all', label:'All', icon:'◈' }, { id:'retrieval', label:'Retrieval', icon:'⊕' },
|
|
13680
|
+
{ id:'analysis', label:'Analysis', icon:'◉' }, { id:'domain-specific', label:'Domain', icon:'◆' },
|
|
13681
|
+
{ id:'ingestion', label:'Ingestion', icon:'⊞' }, { id:'utility', label:'Utility', icon:'⊡' },
|
|
13682
|
+
{ id:'integration', label:'Integration', icon:'⊗' },
|
|
13683
|
+
];
|
|
13684
|
+
// Hardcoded fallback data matching the spec's 20 workflows
|
|
13685
|
+
const WF_STORE_FALLBACK = [
|
|
13686
|
+
{ name:'model-shootout', packageName:'@vaicli/vai-workflow-model-shootout', description:'Compare voyage-4-large, voyage-4, and voyage-4-lite side-by-side on your data.', category:'utility', tags:['benchmarking','model-comparison','cost','shared-space'], tools:['query','estimate','similarity','generate'], steps:7, tier:'official', downloads:3241, featured:true, installed:false, gradient:'linear-gradient(135deg, #0D9488, #06B6D4)' },
|
|
13687
|
+
{ name:'asymmetric-search', packageName:'@vaicli/vai-workflow-asymmetric-search', description:'The canonical shared embedding space demo. Embed with voyage-4-large, query with voyage-4-lite. ~83% cost reduction.', category:'retrieval', tags:['asymmetric','shared-space','cost-savings'], tools:['embed','search','rerank'], steps:3, tier:'official', downloads:4812, featured:true, installed:false, gradient:'linear-gradient(135deg, #00D4AA, #40E0FF)' },
|
|
13688
|
+
{ name:'cost-optimizer', packageName:'@vaicli/vai-workflow-cost-optimizer', description:'Quantify the exact cost savings of asymmetric retrieval on your collection.', category:'utility', tags:['cost','optimization','asymmetric'], tools:['query','estimate','similarity','generate'], steps:6, tier:'official', downloads:2918, featured:true, installed:false, gradient:'linear-gradient(135deg, #F59E0B, #EF4444)' },
|
|
13689
|
+
{ name:'question-decomposition', packageName:'@vaicli/vai-workflow-question-decomposition', description:'Break complex questions into focused sub-queries, search each in parallel, merge and rerank.', category:'retrieval', tags:['decomposition','fan-out','synthesis'], tools:['generate','query','merge','rerank'], steps:6, tier:'official', downloads:1876, installed:false, gradient:'linear-gradient(135deg, #8B5CF6, #EC4899)' },
|
|
13690
|
+
{ name:'contract-clause-finder', packageName:'@vaicli/vai-workflow-contract-clause-finder', description:'Search legal documents for specific clause types using voyage-law-2.', category:'domain-specific', tags:['legal','contracts','voyage-law-2'], tools:['query','rerank','generate'], steps:3, tier:'official', downloads:1543, installed:false, gradient:'linear-gradient(135deg, #1E40AF, #7C3AED)' },
|
|
13691
|
+
{ name:'knowledge-base-bootstrap', packageName:'@vaicli/vai-workflow-knowledge-base-bootstrap', description:'End-to-end onboarding: ingest documents, verify, test a query, and generate a status report.', category:'integration', tags:['onboarding','bootstrap','end-to-end'], tools:['ingest','collections','query','generate'], steps:4, tier:'official', downloads:3654, installed:false, gradient:'linear-gradient(135deg, #059669, #10B981)' },
|
|
13692
|
+
{ name:'embedding-drift-detector', packageName:'@vaicli/vai-workflow-embedding-drift-detector', description:'Monitor embedding quality over time. Re-embed sample documents and compare against stored vectors.', category:'analysis', tags:['monitoring','drift','operations'], tools:['search','embed','similarity','generate'], steps:5, tier:'official', downloads:987, installed:false, gradient:'linear-gradient(135deg, #DC2626, #F97316)' },
|
|
13693
|
+
{ name:'multilingual-search', packageName:'@vaicli/vai-workflow-multilingual-search', description:'Translate your query into multiple languages, search each in parallel, merge and rerank.', category:'retrieval', tags:['multilingual','translation','cross-lingual'], tools:['generate','query','merge','rerank'], steps:5, tier:'official', downloads:1234, installed:false, gradient:'linear-gradient(135deg, #0EA5E9, #6366F1)' },
|
|
13694
|
+
{ name:'financial-risk-scanner', packageName:'@vaicli/vai-workflow-financial-risk-scanner', description:'Scan financial documents for risk signals using voyage-finance-2.', category:'domain-specific', tags:['finance','risk','voyage-finance-2'], tools:['query','rerank','filter','generate'], steps:4, tier:'official', downloads:1102, installed:false, gradient:'linear-gradient(135deg, #B45309, #D97706)' },
|
|
13695
|
+
{ name:'doc-freshness', packageName:'@vaicli/vai-workflow-doc-freshness', description:'Audit your knowledge base for stale content. Gathers metadata from 5 tools in parallel.', category:'utility', tags:['freshness','maintenance','monitoring'], tools:['collections','models','search','explain','estimate','generate'], steps:6, tier:'official', downloads:1456, installed:false, gradient:'linear-gradient(135deg, #4338CA, #7C3AED)' },
|
|
13696
|
+
{ name:'incremental-sync', packageName:'@vaicli/vai-workflow-incremental-sync', description:'Smart ingestion with deduplication. Checks similarity before storing.', category:'ingestion', tags:['dedup','sync','conditional'], tools:['search','similarity','ingest','filter','generate'], steps:5, tier:'official', downloads:2103, installed:false, gradient:'linear-gradient(135deg, #15803D, #4ADE80)' },
|
|
13697
|
+
{ name:'rag-ab-test', packageName:'@vaicli/vai-workflow-rag-ab-test', description:'Run the same query through two RAG configurations side-by-side.', category:'integration', tags:['ab-test','comparison','evaluation'], tools:['query','generate'], steps:5, tier:'official', downloads:1678, installed:false, gradient:'linear-gradient(135deg, #BE185D, #F472B6)' },
|
|
13698
|
+
{ name:'hybrid-precision-search', packageName:'@vaicli/vai-workflow-hybrid-precision-search', description:'Three retrieval strategies in parallel — lite, large, and filtered — merged and reranked.', category:'retrieval', tags:['hybrid','parallel','precision'], tools:['query','search','merge','rerank'], steps:5, tier:'official', downloads:1890, installed:false, gradient:'linear-gradient(135deg, #0891B2, #22D3EE)' },
|
|
13699
|
+
{ name:'code-migration-helper', packageName:'@vaicli/vai-workflow-code-migration-helper', description:'Find similar code patterns across your codebase using voyage-code-3.', category:'domain-specific', tags:['code','migration','voyage-code-3'], tools:['query','rerank','generate'], steps:3, tier:'official', downloads:932, installed:false, gradient:'linear-gradient(135deg, #475569, #94A3B8)' },
|
|
13700
|
+
{ name:'meeting-action-items', packageName:'@vaicli/vai-workflow-meeting-action-items', description:'Ingest meeting notes and extract structured action items with owners, deadlines, and context.', category:'domain-specific', tags:['meetings','action-items','productivity'], tools:['ingest','query','generate'], steps:3, tier:'official', downloads:2567, installed:false, gradient:'linear-gradient(135deg, #7C2D12, #EA580C)' },
|
|
13701
|
+
{ name:'collection-overlap-audit', packageName:'@vaicli/vai-workflow-collection-overlap-audit', description:'Detect duplicate content across two collections.', category:'analysis', tags:['dedup','overlap','audit'], tools:['search','similarity','merge','filter','generate'], steps:5, tier:'official', downloads:678, installed:false, gradient:'linear-gradient(135deg, #6D28D9, #A78BFA)' },
|
|
13702
|
+
{ name:'query-quality-scorer', packageName:'@vaicli/vai-workflow-query-quality-scorer', description:'Evaluate retrieval quality without ground truth labels.', category:'analysis', tags:['evaluation','quality','scoring'], tools:['query','similarity','transform','generate'], steps:4, tier:'official', downloads:823, installed:false, gradient:'linear-gradient(135deg, #9333EA, #C084FC)' },
|
|
13703
|
+
{ name:'clinical-protocol-match', packageName:'@vaicli/vai-workflow-clinical-protocol-match', description:'Match clinical presentations to treatment protocols.', category:'domain-specific', tags:['clinical','healthcare','protocols'], tools:['query','rerank','generate'], steps:3, tier:'official', downloads:712, installed:false, gradient:'linear-gradient(135deg, #0F766E, #2DD4BF)' },
|
|
13704
|
+
{ name:'batch-quality-gate', packageName:'@vaicli/vai-workflow-batch-quality-gate', description:'Quality-filtered ingestion. Only ingest content that meets your similarity threshold.', category:'ingestion', tags:['quality','gate','filtering'], tools:['search','embed','similarity','ingest','filter','generate'], steps:5, tier:'official', downloads:543, installed:false, gradient:'linear-gradient(135deg, #166534, #86EFAC)' },
|
|
13705
|
+
{ name:'index-health-check', packageName:'@vaicli/vai-workflow-index-health-check', description:'Diagnostic pipeline for your vector index.', category:'utility', tags:['diagnostics','health','index'], tools:['collections','search','query','estimate','generate'], steps:5, tier:'official', downloads:1345, installed:false, gradient:'linear-gradient(135deg, #1D4ED8, #60A5FA)' },
|
|
13706
|
+
];
|
|
13707
|
+
|
|
13708
|
+
// Inject branding into fallback data (matches DEFAULT_BRANDING in catalog API)
|
|
13709
|
+
const _WF_FALLBACK_BRANDING = {
|
|
13710
|
+
'model-shootout': { icon:'trophy', color:'#0D9488', iconPath:'M6 9H4.5a2.5 2.5 0 0 1 0-5H6M18 9h1.5a2.5 2.5 0 0 0 0-5H18M4 22h16M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22M18 2H6v7a6 6 0 0 0 12 0V2z' },
|
|
13711
|
+
'asymmetric-search': { icon:'split', color:'#00D4AA', iconPath:'M16 3h5v5M8 3H3v5M12 22v-8.3a4 4 0 0 0-1.172-2.872L3 3M21 3l-7.828 7.828A4 4 0 0 0 12 13.7V22' },
|
|
13712
|
+
'cost-optimizer': { icon:'dollar-sign', color:'#F59E0B', iconPath:'M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6' },
|
|
13713
|
+
'question-decomposition': { icon:'sparkle', color:'#8B5CF6', iconPath:'M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z' },
|
|
13714
|
+
'contract-clause-finder': { icon:'file-search', color:'#1E40AF', iconPath:'M14 2v4a2 2 0 0 0 2 2h4M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7zM9.5 12.5a2.5 2.5 0 1 0 5 0 2.5 2.5 0 1 0-5 0M13.3 14.3 15 16' },
|
|
13715
|
+
'knowledge-base-bootstrap': { icon:'database', color:'#059669', iconPath:'M21 5c0 1.1-3.134 3-9 3S3 6.1 3 5M21 5c0-1.1-3.134-3-9-3S3 3.9 3 5M21 5v14c0 1.1-3.134 3-9 3s-9-1.9-9-3V5M21 12c0 1.1-3.134 3-9 3s-9-1.9-9-3' },
|
|
13716
|
+
'embedding-drift-detector': { icon:'activity', color:'#DC2626', iconPath:'M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36-3.18-19.64A2 2 0 0 0 10.12 1h-.24a2 2 0 0 0-1.94 1.55L5.18 12H2' },
|
|
13717
|
+
'multilingual-search': { icon:'globe', color:'#0EA5E9', iconPath:'M12 12m-10 0a10 10 0 1 0 20 0 10 10 0 1 0-20 0M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z' },
|
|
13718
|
+
'financial-risk-scanner': { icon:'shield-alert', color:'#B45309', iconPath:'M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 .5-.87l7-4a1 1 0 0 1 1 0l7 4A1 1 0 0 1 20 6zM12 8v4M12 16h.01' },
|
|
13719
|
+
'doc-freshness': { icon:'timer', color:'#4338CA', iconPath:'M10 2h4M12 14l3-3M12 22a8 8 0 1 0 0-16 8 8 0 0 0 0 16z' },
|
|
13720
|
+
'incremental-sync': { icon:'refresh-cw', color:'#15803D', iconPath:'M21 2v6h-6M3 12a9 9 0 0 1 15-6.7L21 8M3 22v-6h6M21 12a9 9 0 0 1-15 6.7L3 16' },
|
|
13721
|
+
'rag-ab-test': { icon:'flask-conical', color:'#BE185D', iconPath:'M10 2v7.527a2 2 0 0 1-.211.896L4.72 20.55a1 1 0 0 0 .9 1.45h12.76a1 1 0 0 0 .9-1.45l-5.069-10.127A2 2 0 0 1 14 9.527V2M8.5 2h7M7 16h10' },
|
|
13722
|
+
'hybrid-precision-search': { icon:'target', color:'#0891B2', iconPath:'M12 12m-10 0a10 10 0 1 0 20 0 10 10 0 1 0-20 0M12 12m-6 0a6 6 0 1 0 12 0 6 6 0 1 0-12 0M12 12m-2 0a2 2 0 1 0 4 0 2 2 0 1 0-4 0' },
|
|
13723
|
+
'code-migration-helper': { icon:'code', color:'#475569', iconPath:'M16 18l6-6-6-6M8 6l-6 6 6 6' },
|
|
13724
|
+
'meeting-action-items': { icon:'clipboard-list',color:'#7C2D12', iconPath:'M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2M9 2h6a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1zM12 11h4M12 16h4M8 11h.01M8 16h.01' },
|
|
13725
|
+
'collection-overlap-audit': { icon:'layers', color:'#6D28D9', iconPath:'M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.84zM2 12l8.58 3.91a2 2 0 0 0 1.66 0L22 12M2 17l8.58 3.91a2 2 0 0 0 1.66 0L22 17' },
|
|
13726
|
+
'query-quality-scorer': { icon:'microscope', color:'#9333EA', iconPath:'M6 18h8M3 22h18M14 22a7 7 0 1 0 0-14h-1M9 14h2M9 12a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2z' },
|
|
13727
|
+
'clinical-protocol-match': { icon:'heart-pulse', color:'#0F766E', iconPath:'M19.5 12.572l-7.5 7.428-7.5-7.428A5 5 0 0 1 7.5 5c1.8 0 3.3.9 4.5 2.7C13.2 5.9 14.7 5 16.5 5a5 5 0 0 1 3 9.572zM12 6l-1 4h4l-1 4' },
|
|
13728
|
+
'batch-quality-gate': { icon:'check-circle', color:'#166534', iconPath:'M22 11.08V12a10 10 0 1 1-5.93-9.14M22 4 12 14.01l-3-3' },
|
|
13729
|
+
'index-health-check': { icon:'bar-chart-3', color:'#1D4ED8', iconPath:'M12 20V10M18 20V4M6 20v-4' },
|
|
13730
|
+
};
|
|
13731
|
+
WF_STORE_FALLBACK.forEach(wf => { wf.branding = _WF_FALLBACK_BRANDING[wf.name] || { icon:'zap', color:'#64748B', iconPath:'M13 2 3 14h9l-1 8 10-12h-9l1-8z' }; });
|
|
13732
|
+
|
|
13733
|
+
let _wfStoreCatalog = null;
|
|
13734
|
+
let _wfStoreCat = 'all';
|
|
13735
|
+
|
|
13736
|
+
function wfStoreOpen() {
|
|
13737
|
+
const overlay = document.getElementById('wfStoreOverlay');
|
|
13738
|
+
if (!overlay) return;
|
|
13739
|
+
overlay.classList.add('open');
|
|
13740
|
+
document.getElementById('wfStoreBtn').classList.add('active');
|
|
13741
|
+
if (!_wfStoreCatalog) wfStoreFetchCatalog();
|
|
13742
|
+
}
|
|
13743
|
+
|
|
13744
|
+
function wfStoreClose() {
|
|
13745
|
+
const overlay = document.getElementById('wfStoreOverlay');
|
|
13746
|
+
if (overlay) overlay.classList.remove('open');
|
|
13747
|
+
document.getElementById('wfStoreBtn').classList.remove('active');
|
|
13748
|
+
// Close detail modal if open
|
|
13749
|
+
const detail = document.getElementById('wfStoreDetail');
|
|
13750
|
+
if (detail) detail.remove();
|
|
13751
|
+
}
|
|
13752
|
+
|
|
13753
|
+
// Store icons map (populated from API response or used from inline fallback)
|
|
13754
|
+
let _wfStoreIcons = {};
|
|
13755
|
+
|
|
13756
|
+
// Render a branding icon as inline SVG
|
|
13757
|
+
function wfBrandingIcon(wf, size = 16) {
|
|
13758
|
+
const b = wf.branding || {};
|
|
13759
|
+
const iconPath = b.iconPath || _wfStoreIcons[b.icon] || '';
|
|
13760
|
+
if (!iconPath) return '';
|
|
13761
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="${iconPath}"/></svg>`;
|
|
13762
|
+
}
|
|
13763
|
+
|
|
13764
|
+
async function wfStoreFetchCatalog() {
|
|
13765
|
+
try {
|
|
13766
|
+
const res = await fetch('/api/workflows/catalog');
|
|
13767
|
+
const data = await res.json();
|
|
13768
|
+
if (data.icons) _wfStoreIcons = data.icons;
|
|
13769
|
+
if (data.workflows && data.workflows.length > 0) {
|
|
13770
|
+
_wfStoreCatalog = data.workflows;
|
|
13771
|
+
} else {
|
|
13772
|
+
_wfStoreCatalog = WF_STORE_FALLBACK;
|
|
13773
|
+
}
|
|
13774
|
+
} catch {
|
|
13775
|
+
_wfStoreCatalog = WF_STORE_FALLBACK;
|
|
13776
|
+
}
|
|
13777
|
+
wfStoreRender();
|
|
13778
|
+
}
|
|
13779
|
+
|
|
13780
|
+
function wfStoreRender() {
|
|
13781
|
+
const container = document.getElementById('wfStoreContent');
|
|
13782
|
+
if (!container || !_wfStoreCatalog) return;
|
|
13783
|
+
const search = (document.getElementById('wfStoreSearch')?.value || '').toLowerCase();
|
|
13784
|
+
const sort = document.getElementById('wfStoreSort')?.value || 'downloads';
|
|
13785
|
+
|
|
13786
|
+
const filtered = _wfStoreCatalog.filter(wf => {
|
|
13787
|
+
const mc = _wfStoreCat === 'all' || wf.category === _wfStoreCat;
|
|
13788
|
+
const ms = !search || wf.name.includes(search) || (wf.description||'').toLowerCase().includes(search)
|
|
13789
|
+
|| (wf.tags||[]).some(t => t.includes(search)) || (wf.tools||[]).some(t => t.includes(search));
|
|
13790
|
+
return mc && ms;
|
|
13791
|
+
}).sort((a, b) => sort === 'downloads' ? (b.downloads||0) - (a.downloads||0) : sort === 'name' ? a.name.localeCompare(b.name) : (b.steps||0) - (a.steps||0));
|
|
13792
|
+
|
|
13793
|
+
const featured = _wfStoreCatalog.filter(w => w.featured);
|
|
13794
|
+
const showFeat = _wfStoreCat === 'all' && !search;
|
|
13795
|
+
|
|
13796
|
+
let html = '';
|
|
13797
|
+
// Category chips
|
|
13798
|
+
html += '<div class="wf-store-chips">';
|
|
13799
|
+
WF_STORE_CATEGORIES.forEach(c => {
|
|
13800
|
+
html += `<button class="wf-store-chip ${_wfStoreCat===c.id?'active':''}" onclick="_wfStoreCat='${c.id}';wfStoreRender()">${c.icon} ${c.label}</button>`;
|
|
13801
|
+
});
|
|
13802
|
+
html += '</div>';
|
|
13803
|
+
|
|
13804
|
+
// Featured section
|
|
13805
|
+
if (showFeat && featured.length > 0) {
|
|
13806
|
+
html += '<div class="wf-store-section-label">Featured</div>';
|
|
13807
|
+
html += '<div class="wf-store-featured-grid">';
|
|
13808
|
+
featured.forEach(wf => {
|
|
13809
|
+
const featAuthor = wf.author && wf.author.name && wf.author.name !== 'unknown' ? wf.author : null;
|
|
13810
|
+
const featAvatarHtml = featAuthor ? (featAuthor.avatar
|
|
13811
|
+
? `<div class="wf-store-featured-avatar"><img src="${featAuthor.avatar}" onerror="this.parentNode.textContent='${featAuthor.name.charAt(0).toUpperCase()}'"></div>`
|
|
13812
|
+
: `<div class="wf-store-featured-avatar">${featAuthor.name.charAt(0).toUpperCase()}</div>`) : '';
|
|
13813
|
+
const featAuthorHtml = featAuthor ? `<div class="wf-store-featured-author">${featAvatarHtml} by ${featAuthor.name}</div>` : '';
|
|
13814
|
+
const featIconHtml = wfBrandingIcon(wf, 20) ? `<div class="wf-store-featured-icon">${wfBrandingIcon(wf, 20)}</div>` : '';
|
|
13815
|
+
html += `<div class="wf-store-featured-card" style="background:${wf.gradient}" onclick="wfStoreShowDetail('${wf.name}')">
|
|
13816
|
+
${featIconHtml}
|
|
13817
|
+
<div class="wf-store-featured-dl">↓ ${(wf.downloads||0).toLocaleString()}</div>
|
|
13818
|
+
<div class="wf-store-featured-content"><h3>${wf.name}</h3><p>${wf.description||''}</p>${featAuthorHtml}</div>
|
|
13819
|
+
</div>`;
|
|
13820
|
+
});
|
|
13821
|
+
html += '</div>';
|
|
13822
|
+
}
|
|
13823
|
+
|
|
13824
|
+
// Grid label
|
|
13825
|
+
const label = _wfStoreCat === 'all' ? 'All Workflows' : (WF_STORE_CATEGORIES.find(c=>c.id===_wfStoreCat)?.label || _wfStoreCat);
|
|
13826
|
+
html += `<div class="wf-store-section-label">${label} <span class="wf-store-count">${filtered.length}</span></div>`;
|
|
13827
|
+
|
|
13828
|
+
if (filtered.length === 0) {
|
|
13829
|
+
html += '<div class="wf-store-empty"><p>No workflows match your search</p></div>';
|
|
13830
|
+
} else {
|
|
13831
|
+
html += '<div class="wf-store-grid">';
|
|
13832
|
+
filtered.forEach(wf => {
|
|
13833
|
+
const dots = (wf.tools||[]).slice(0,5).map(t =>
|
|
13834
|
+
`<div class="wf-store-card-dot" style="background:${WF_STORE_TOOL_COLORS[t]||'#666'}"></div>`
|
|
13835
|
+
).join('') + ((wf.tools||[]).length > 5 ? `<span style="font-family:var(--mono);font-size:9px;color:var(--text-muted);margin-left:2px">+${wf.tools.length-5}</span>` : '');
|
|
13836
|
+
const badges = (wf.installed ? '<span class="wf-store-badge-installed">installed</span>' : '') +
|
|
13837
|
+
`<span class="wf-store-badge-official">${wf.tier==='official' ? '✓ OFFICIAL' : 'COMMUNITY'}</span>`;
|
|
13838
|
+
const cardIconColor = (wf.branding && wf.branding.color) || '#64748B';
|
|
13839
|
+
const cardIconSvg = wfBrandingIcon(wf, 16);
|
|
13840
|
+
const cardIconHtml = cardIconSvg
|
|
13841
|
+
? `<div class="wf-store-card-icon" style="background:${cardIconColor}18;color:${cardIconColor}">${cardIconSvg}</div>`
|
|
13842
|
+
: '';
|
|
13843
|
+
html += `<div class="wf-store-card" onclick="wfStoreShowDetail('${wf.name}')">
|
|
13844
|
+
<div class="wf-store-card-bar" style="background:${wf.gradient}"></div>
|
|
13845
|
+
<div class="wf-store-card-top">
|
|
13846
|
+
<div class="wf-store-card-name-wrap">${cardIconHtml}<span class="wf-store-card-name">${wf.name}</span></div>
|
|
13847
|
+
<div class="wf-store-card-badges">${badges}</div>
|
|
13848
|
+
</div>
|
|
13849
|
+
<div class="wf-store-card-desc">${wf.description||''}</div>
|
|
13850
|
+
${wf.author && wf.author.name && wf.author.name !== 'unknown' ? `<div class="wf-store-card-author">by ${wf.author.name}</div>` : ''}
|
|
13851
|
+
<div class="wf-store-card-meta">
|
|
13852
|
+
<span class="wf-store-card-cat">${wf.category||'utility'}</span>
|
|
13853
|
+
<div class="wf-store-card-dots">${dots}</div>
|
|
13854
|
+
<span class="wf-store-card-complexity">${wf.steps||0}s·${wf.toolCount||(Array.isArray(wf.tools)?wf.tools.length:0)}t</span>
|
|
13855
|
+
<span class="wf-store-card-downloads">↓ ${(wf.downloads||0).toLocaleString()}</span>
|
|
13856
|
+
</div>
|
|
13857
|
+
</div>`;
|
|
13858
|
+
});
|
|
13859
|
+
html += '</div>';
|
|
13860
|
+
}
|
|
13861
|
+
|
|
13862
|
+
container.innerHTML = html;
|
|
13863
|
+
}
|
|
13864
|
+
|
|
13865
|
+
function wfStoreShowDetail(name) {
|
|
13866
|
+
const wf = (_wfStoreCatalog || []).find(w => w.name === name);
|
|
13867
|
+
if (!wf) return;
|
|
13868
|
+
// Remove existing detail
|
|
13869
|
+
const existing = document.getElementById('wfStoreDetail');
|
|
13870
|
+
if (existing) existing.remove();
|
|
13871
|
+
|
|
13872
|
+
const ic = `vai workflow install ${wf.packageName}`;
|
|
13873
|
+
const rc = `vai workflow run ${wf.packageName}`;
|
|
13874
|
+
|
|
13875
|
+
// Escape for use in HTML attributes (single-quoted onclick handlers)
|
|
13876
|
+
const icEsc = ic.replace(/'/g, "\\'");
|
|
13877
|
+
const rcEsc = rc.replace(/'/g, "\\'");
|
|
13878
|
+
|
|
13879
|
+
const toolsHtml = (wf.tools||[]).map(t => {
|
|
13880
|
+
const c = WF_STORE_TOOL_COLORS[t] || '#666';
|
|
13881
|
+
return `<span class="wf-store-detail-tool" style="color:${c};border-color:${c}44;background:${c}11">${t}</span>`;
|
|
13882
|
+
}).join('');
|
|
13883
|
+
|
|
13884
|
+
const tagsHtml = `<span class="wf-store-detail-tag">${wf.category||'utility'}</span>` +
|
|
13885
|
+
(wf.tags||[]).map(t => `<span class="wf-store-detail-tag">${t}</span>`).join('');
|
|
13886
|
+
|
|
13887
|
+
const installBtn = wf.installed
|
|
13888
|
+
? '<button class="wf-store-detail-btn wf-store-detail-btn-installed">✓ Installed</button>'
|
|
13889
|
+
: `<button class="wf-store-detail-btn wf-store-detail-btn-primary" onclick="wfStoreInstall('${wf.packageName}',this)">Install</button>`;
|
|
13890
|
+
|
|
13891
|
+
const modal = document.createElement('div');
|
|
13892
|
+
modal.id = 'wfStoreDetail';
|
|
13893
|
+
modal.className = 'wf-store-detail-bg';
|
|
13894
|
+
modal.onclick = (e) => { if (e.target === modal) modal.remove(); };
|
|
13895
|
+
modal.innerHTML = `<div class="wf-store-detail-panel" onclick="event.stopPropagation()">
|
|
13896
|
+
<div class="wf-store-detail-hero" style="background:${wf.gradient}">
|
|
13897
|
+
<button class="wf-store-detail-close" onclick="document.getElementById('wfStoreDetail').remove()">×</button>
|
|
13898
|
+
<div class="wf-store-detail-hero-inner">
|
|
13899
|
+
${wfBrandingIcon(wf, 26) ? `<div class="wf-store-detail-hero-icon">${wfBrandingIcon(wf, 26)}</div>` : ''}
|
|
13900
|
+
<div>
|
|
13901
|
+
<div class="wf-store-detail-name">${wf.name}</div>
|
|
13902
|
+
<div class="wf-store-detail-pkg">${wf.packageName}</div>
|
|
13903
|
+
</div>
|
|
13904
|
+
</div>
|
|
13905
|
+
</div>
|
|
13906
|
+
<div class="wf-store-detail-body">
|
|
13907
|
+
${(() => {
|
|
13908
|
+
const a = wf.author && wf.author.name && wf.author.name !== 'unknown' ? wf.author : null;
|
|
13909
|
+
if (!a) return '';
|
|
13910
|
+
const avatarInner = a.avatar
|
|
13911
|
+
? `<img src="${a.avatar}" onerror="this.parentNode.textContent='${a.name.charAt(0).toUpperCase()}'">`
|
|
13912
|
+
: a.name.charAt(0).toUpperCase();
|
|
13913
|
+
const nameHtml = a.url
|
|
13914
|
+
? `<a href="${a.url}" target="_blank" rel="noopener">${a.name}</a>`
|
|
13915
|
+
: a.name;
|
|
13916
|
+
return `<div class="wf-store-detail-author"><div class="wf-store-detail-avatar">${avatarInner}</div><div class="wf-store-detail-author-name">${nameHtml}</div></div>`;
|
|
13917
|
+
})()}
|
|
13918
|
+
<p class="wf-store-detail-desc">${wf.description||''}</p>
|
|
13919
|
+
${wf.assets && wf.assets.screenshots && wf.assets.screenshots.length > 0 ? `
|
|
13920
|
+
<div class="wf-store-detail-section">
|
|
13921
|
+
<div class="wf-store-detail-label">Screenshots</div>
|
|
13922
|
+
<div class="wf-store-detail-screenshots">${wf.assets.screenshots.map(s => `<img src="${s}" alt="Screenshot">`).join('')}</div>
|
|
13923
|
+
</div>` : ''}
|
|
13924
|
+
<div class="wf-store-detail-section">
|
|
13925
|
+
<div class="wf-store-detail-label">Stats</div>
|
|
13926
|
+
<div class="wf-store-detail-stats">
|
|
13927
|
+
<div class="wf-store-detail-stat"><div class="wf-store-detail-stat-val">${wf.steps||0}</div><div class="wf-store-detail-stat-lbl">Steps</div></div>
|
|
13928
|
+
<div class="wf-store-detail-stat"><div class="wf-store-detail-stat-val">${wf.toolCount||(Array.isArray(wf.tools)?wf.tools.length:0)}</div><div class="wf-store-detail-stat-lbl">Tools</div></div>
|
|
13929
|
+
<div class="wf-store-detail-stat"><div class="wf-store-detail-stat-val">${(wf.downloads||0).toLocaleString()}</div><div class="wf-store-detail-stat-lbl">Downloads</div></div>
|
|
13930
|
+
</div>
|
|
13931
|
+
</div>
|
|
13932
|
+
<div class="wf-store-detail-section">
|
|
13933
|
+
<div class="wf-store-detail-label">Tools</div>
|
|
13934
|
+
<div class="wf-store-detail-tools">${toolsHtml}</div>
|
|
13935
|
+
</div>
|
|
13936
|
+
<div class="wf-store-detail-section">
|
|
13937
|
+
<div class="wf-store-detail-label">Tags</div>
|
|
13938
|
+
<div class="wf-store-detail-tags">${tagsHtml}</div>
|
|
13939
|
+
</div>
|
|
13940
|
+
${wf.inputs && wf.inputs.length > 0 ? `
|
|
13941
|
+
<div class="wf-store-detail-section">
|
|
13942
|
+
<div class="wf-store-detail-label">Input Parameters</div>
|
|
13943
|
+
<table class="wf-store-detail-inputs-table">
|
|
13944
|
+
<thead><tr><th>Name</th><th>Type</th><th>Required / Default</th></tr></thead>
|
|
13945
|
+
<tbody>${wf.inputs.map(inp => `<tr><td>${inp.name}</td><td>${inp.type}</td><td>${inp.required ? 'required' : inp.default !== undefined ? 'default: ' + inp.default : 'optional'}</td></tr>`).join('')}</tbody>
|
|
13946
|
+
</table>
|
|
13947
|
+
</div>` : ''}
|
|
13948
|
+
<div class="wf-store-detail-install">
|
|
13949
|
+
<div class="wf-store-detail-label">Install</div>
|
|
13950
|
+
<div class="wf-store-detail-cmd" onclick="navigator.clipboard.writeText('${icEsc}');this.querySelector('.wf-store-detail-cmd-hint').textContent='✓ copied';setTimeout(()=>this.querySelector('.wf-store-detail-cmd-hint').textContent='copy',2000)">
|
|
13951
|
+
<span>$ ${ic}</span><span class="wf-store-detail-cmd-hint">copy</span>
|
|
13952
|
+
</div>
|
|
13953
|
+
<div class="wf-store-detail-label" style="margin-top:12px">Run</div>
|
|
13954
|
+
<div class="wf-store-detail-cmd" style="color:var(--text-dim)" onclick="navigator.clipboard.writeText('${rcEsc}');this.querySelector('.wf-store-detail-cmd-hint').textContent='✓ copied';setTimeout(()=>this.querySelector('.wf-store-detail-cmd-hint').textContent='copy',2000)">
|
|
13955
|
+
<span>$ ${rc}</span><span class="wf-store-detail-cmd-hint">copy</span>
|
|
13956
|
+
</div>
|
|
13957
|
+
</div>
|
|
13958
|
+
<div class="wf-store-detail-actions">
|
|
13959
|
+
${installBtn}
|
|
13960
|
+
<button class="wf-store-detail-btn wf-store-detail-btn-secondary" onclick="wfStoreRun('${wf.name}')">Run</button>
|
|
13961
|
+
<button class="wf-store-detail-btn wf-store-detail-btn-secondary" onclick="wfStoreCanvas('${wf.name}')">Canvas</button>
|
|
13962
|
+
</div>
|
|
13963
|
+
</div>
|
|
13964
|
+
</div>`;
|
|
13965
|
+
document.body.appendChild(modal);
|
|
13966
|
+
}
|
|
13967
|
+
|
|
13968
|
+
async function wfStoreInstall(packageName, btn) {
|
|
13969
|
+
if (btn) { btn.textContent = 'Installing...'; btn.disabled = true; }
|
|
13970
|
+
try {
|
|
13971
|
+
const res = await fetch('/api/workflows/community/install', {
|
|
13972
|
+
method: 'POST',
|
|
13973
|
+
headers: { 'Content-Type': 'application/json' },
|
|
13974
|
+
body: JSON.stringify({ name: packageName })
|
|
13975
|
+
});
|
|
13976
|
+
const text = await res.text();
|
|
13977
|
+
let data;
|
|
13978
|
+
try { data = JSON.parse(text); } catch { data = { error: text || 'Empty response (status ' + res.status + ')' }; }
|
|
13979
|
+
console.log('[Store Install]', packageName, data);
|
|
13980
|
+
if (data.success) {
|
|
13981
|
+
if (btn) { btn.textContent = '✓ Installed'; btn.className = 'wf-store-detail-btn wf-store-detail-btn-installed'; btn.disabled = true; }
|
|
13982
|
+
// Update catalog state
|
|
13983
|
+
const wf = (_wfStoreCatalog||[]).find(w => w.packageName === packageName);
|
|
13984
|
+
if (wf) wf.installed = true;
|
|
13985
|
+
wfStoreRender();
|
|
13986
|
+
await wfLoadLibrary(); // Refresh library panel
|
|
13987
|
+
} else {
|
|
13988
|
+
const errMsg = data.error || 'Unknown error';
|
|
13989
|
+
console.error('[Store Install Error]', errMsg);
|
|
13990
|
+
if (btn) { btn.textContent = 'Failed'; btn.title = errMsg; setTimeout(() => { btn.textContent = 'Install'; btn.disabled = false; btn.title = ''; }, 4000); }
|
|
13991
|
+
}
|
|
13992
|
+
} catch (err) {
|
|
13993
|
+
console.error('[Store Install Exception]', err);
|
|
13994
|
+
if (btn) { btn.textContent = 'Failed'; btn.title = err.message; setTimeout(() => { btn.textContent = 'Install'; btn.disabled = false; btn.title = ''; }, 4000); }
|
|
13995
|
+
}
|
|
13996
|
+
}
|
|
13997
|
+
|
|
13998
|
+
async function wfStoreRun(name) {
|
|
13999
|
+
const detail = document.getElementById('wfStoreDetail');
|
|
14000
|
+
if (detail) detail.remove();
|
|
14001
|
+
wfStoreClose();
|
|
14002
|
+
|
|
14003
|
+
// Find the workflow in catalog
|
|
14004
|
+
const wf = (_wfStoreCatalog||[]).find(w => w.name === name);
|
|
14005
|
+
if (!wf) {
|
|
14006
|
+
alert('Workflow not found: ' + name);
|
|
14007
|
+
return;
|
|
14008
|
+
}
|
|
14009
|
+
|
|
14010
|
+
// Check if installed
|
|
14011
|
+
if (!wf.installed) {
|
|
14012
|
+
const doInstall = confirm(`"${wf.name}" is not installed. Install it now?`);
|
|
14013
|
+
if (doInstall) {
|
|
14014
|
+
await wfStoreInstall(wf.packageName, null);
|
|
14015
|
+
// Refresh catalog to update installed status
|
|
14016
|
+
await wfStoreFetchCatalog();
|
|
14017
|
+
// Check again
|
|
14018
|
+
const updated = (_wfStoreCatalog||[]).find(w => w.name === name);
|
|
14019
|
+
if (!updated || !updated.installed) {
|
|
14020
|
+
alert('Installation may have failed. Please try again.');
|
|
14021
|
+
return;
|
|
14022
|
+
}
|
|
14023
|
+
} else {
|
|
14024
|
+
return;
|
|
14025
|
+
}
|
|
14026
|
+
}
|
|
14027
|
+
|
|
14028
|
+
// Switch to workflows tab and load the workflow
|
|
14029
|
+
switchTab('workflows');
|
|
14030
|
+
// Use the full package name for resolution
|
|
14031
|
+
wfSelectWorkflow(wf.packageName);
|
|
14032
|
+
}
|
|
14033
|
+
|
|
14034
|
+
function wfStoreCanvas(name) {
|
|
14035
|
+
wfStoreRun(name); // Same behavior — load the workflow into canvas
|
|
14036
|
+
}
|
|
14037
|
+
|
|
14038
|
+
// Escape key handler for store
|
|
14039
|
+
document.addEventListener('keydown', (e) => {
|
|
14040
|
+
if (e.key === 'Escape') {
|
|
14041
|
+
const detail = document.getElementById('wfStoreDetail');
|
|
14042
|
+
if (detail) { detail.remove(); return; }
|
|
14043
|
+
const overlay = document.getElementById('wfStoreOverlay');
|
|
14044
|
+
if (overlay && overlay.classList.contains('open')) { wfStoreClose(); }
|
|
14045
|
+
}
|
|
14046
|
+
});
|
|
14047
|
+
|
|
10973
14048
|
function wfInit() {
|
|
10974
14049
|
wfLoadLibrary();
|
|
10975
14050
|
wfInitPan();
|