free-coding-models 0.5.28 → 0.5.29
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/bin/free-coding-models.js +10 -2
- package/changelog/v0.5.29.md +19 -0
- package/package.json +1 -1
- package/sources.js +3 -2
- package/src/core/config.js +1 -14
- package/src/core/endpoint-installer.js +5 -9
- package/src/core/legacy-proxy-cleanup.js +2 -0
- package/src/core/model-merger.js +4 -13
- package/src/core/router-daemon.js +76 -41
- package/src/core/router-dashboard.js +63 -25
- package/src/core/shared-helpers.js +117 -0
- package/src/core/sync-set.js +6 -21
- package/src/core/tool-launchers.js +24 -92
- package/src/core/utils.js +26 -100
- package/src/tui/app.js +2 -2
- package/src/tui/command-palette.js +1 -0
- package/src/tui/key-handler.js +77 -20
- package/src/tui/render-table.js +3 -3
- package/web/dist/assets/index-BMU58Jju.js +41 -0
- package/web/dist/assets/{index-BRFowJv5.css → index-Dd1jOGEn.css} +1 -1
- package/web/dist/index.html +2 -2
- package/web/src/components/router/RouterView.jsx +124 -56
- package/web/src/components/router/RouterView.module.css +135 -1
- package/src/core/product-flags.js +0 -9
- package/web/dist/assets/index-Pr3waI0-.js +0 -41
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
IconRoute, IconPlayerPlay, IconPlayerStop, IconRefresh,
|
|
10
10
|
IconCopy, IconCheck, IconChevronDown, IconChevronUp,
|
|
11
11
|
IconActivity, IconServer, IconPlus, IconX, IconGripVertical,
|
|
12
|
-
IconArrowUp, IconArrowDown, IconTrash, IconList, IconWand,
|
|
12
|
+
IconArrowRight, IconArrowUp, IconArrowDown, IconTrash, IconList, IconWand,
|
|
13
13
|
} from '@tabler/icons-react'
|
|
14
14
|
import styles from './RouterView.module.css'
|
|
15
15
|
|
|
@@ -72,6 +72,7 @@ export default function RouterView({ onClose, onToast }) {
|
|
|
72
72
|
const [pickerProvider, setPickerProvider] = useState('')
|
|
73
73
|
const [saveStatus, setSaveStatus] = useState(SAVE_STATUS_IDLE)
|
|
74
74
|
const saveTimerRef = useRef(null)
|
|
75
|
+
const hasAutoExpandedLog = useRef(false)
|
|
75
76
|
|
|
76
77
|
const fetchStatus = useCallback(async () => {
|
|
77
78
|
try {
|
|
@@ -133,10 +134,15 @@ export default function RouterView({ onClose, onToast }) {
|
|
|
133
134
|
await fetchStatus()
|
|
134
135
|
await fetchSets()
|
|
135
136
|
} else {
|
|
136
|
-
onToast?.(`Failed to start: ${data.error || 'unknown'}`, 'error')
|
|
137
|
+
onToast?.(`Failed to start: ${data.error || data.message || 'unknown'}`, 'error')
|
|
137
138
|
}
|
|
138
139
|
} catch (err) {
|
|
139
|
-
|
|
140
|
+
// 📖 Distinguish between "daemon not running" (fetch itself fails) and
|
|
141
|
+
// 📖 other network errors so the user gets an actionable error message.
|
|
142
|
+
const msg = err.name === 'TypeError' && err.message?.includes('fetch')
|
|
143
|
+
? 'Cannot reach daemon — it may not be installed or the port is blocked'
|
|
144
|
+
: err.message
|
|
145
|
+
onToast?.(`Start failed: ${msg}`, 'error')
|
|
140
146
|
} finally { setActionLoading(false) }
|
|
141
147
|
}
|
|
142
148
|
|
|
@@ -418,6 +424,29 @@ export default function RouterView({ onClose, onToast }) {
|
|
|
418
424
|
const circuitBreakers = stats?.circuitBreakers || {}
|
|
419
425
|
const requestLog = stats?.requestLog || []
|
|
420
426
|
|
|
427
|
+
// 📖 routingOrder — the exact attempt order the daemon will use for the next
|
|
428
|
+
// 📖 request (priority-first among healthy models). routingOrder[0] is the
|
|
429
|
+
// 📖 model that will serve the next chat. Used to mark the "next" row and
|
|
430
|
+
// 📖 to label Primary vs Fallback semantics. Falls back to set order when the
|
|
431
|
+
// 📖 daemon isn't running or hasn't reported yet (so stopped state still shows
|
|
432
|
+
// 📖 a sensible priority chain). See issue #120.
|
|
433
|
+
const routingOrder = stats?.routingOrder || []
|
|
434
|
+
const nextToServeKey = routingOrder.length > 0 ? routingOrder[0].key : null
|
|
435
|
+
|
|
436
|
+
// 📖 Auto-expand the request log the first time requests appear.
|
|
437
|
+
useEffect(() => {
|
|
438
|
+
if (requestLog.length > 0 && !hasAutoExpandedLog.current) {
|
|
439
|
+
hasAutoExpandedLog.current = true
|
|
440
|
+
setLogExpanded(true)
|
|
441
|
+
}
|
|
442
|
+
}, [requestLog.length])
|
|
443
|
+
|
|
444
|
+
// 📖 Computed quick-setup display values: actual when running, defaults when stopped.
|
|
445
|
+
const qsBaseUrl = running && quickSetup?.baseUrl ? quickSetup.baseUrl : 'http://localhost:19280/v1'
|
|
446
|
+
const qsModel = quickSetup?.model || 'fcm'
|
|
447
|
+
const qsApiKey = 'fcm-local'
|
|
448
|
+
const qsAllText = `Base URL: ${qsBaseUrl}\nModel: ${qsModel}\nAPI Key: ${qsApiKey}`
|
|
449
|
+
|
|
421
450
|
const sets = setsData?.sets || {}
|
|
422
451
|
const setNames = Object.keys(sets).sort()
|
|
423
452
|
|
|
@@ -464,6 +493,41 @@ export default function RouterView({ onClose, onToast }) {
|
|
|
464
493
|
</div>
|
|
465
494
|
)}
|
|
466
495
|
|
|
496
|
+
{/* Quick Setup — ALWAYS visible, front and center */}
|
|
497
|
+
<div className={`${styles.quickSetup} ${running ? styles.quickSetupHero : ''}`}>
|
|
498
|
+
<h3 className={styles.sectionTitle}>
|
|
499
|
+
<IconCopy size={14} />
|
|
500
|
+
Quick Setup
|
|
501
|
+
<button className={styles.copyAllBtn} onClick={() => handleCopy(qsAllText, 'all')}>
|
|
502
|
+
{copied === 'all' ? <IconCheck size={12} /> : <IconCopy size={12} />}
|
|
503
|
+
{copied === 'all' ? 'Copied!' : 'Copy all'}
|
|
504
|
+
</button>
|
|
505
|
+
</h3>
|
|
506
|
+
<div className={styles.quickRows}>
|
|
507
|
+
<div className={styles.quickRow}>
|
|
508
|
+
<span className={styles.quickLabel}>Base URL</span>
|
|
509
|
+
<code className={styles.quickValue}>{qsBaseUrl}</code>
|
|
510
|
+
<button className={styles.copyBtn} onClick={() => handleCopy(qsBaseUrl, 'url')}>
|
|
511
|
+
{copied === 'url' ? <IconCheck size={12} /> : <IconCopy size={12} />}
|
|
512
|
+
</button>
|
|
513
|
+
</div>
|
|
514
|
+
<div className={styles.quickRow}>
|
|
515
|
+
<span className={styles.quickLabel}>Model</span>
|
|
516
|
+
<code className={styles.quickValue}>{qsModel}</code>
|
|
517
|
+
<button className={styles.copyBtn} onClick={() => handleCopy(qsModel, 'model')}>
|
|
518
|
+
{copied === 'model' ? <IconCheck size={12} /> : <IconCopy size={12} />}
|
|
519
|
+
</button>
|
|
520
|
+
</div>
|
|
521
|
+
<div className={styles.quickRow}>
|
|
522
|
+
<span className={styles.quickLabel}>API Key</span>
|
|
523
|
+
<code className={styles.quickValue}>{qsApiKey}</code>
|
|
524
|
+
<button className={styles.copyBtn} onClick={() => handleCopy(qsApiKey, 'key')}>
|
|
525
|
+
{copied === 'key' ? <IconCheck size={12} /> : <IconCopy size={12} />}
|
|
526
|
+
</button>
|
|
527
|
+
</div>
|
|
528
|
+
</div>
|
|
529
|
+
</div>
|
|
530
|
+
|
|
467
531
|
{/* Hero Card */}
|
|
468
532
|
<div className={`${styles.heroCard} ${running ? styles.heroRunning : styles.heroStopped}`}>
|
|
469
533
|
<div className={styles.heroLeft}>
|
|
@@ -488,8 +552,8 @@ export default function RouterView({ onClose, onToast }) {
|
|
|
488
552
|
</div>
|
|
489
553
|
<div className={styles.heroActions}>
|
|
490
554
|
{!running ? (
|
|
491
|
-
<button className={styles.startBtn} onClick={handleStart} disabled={actionLoading}>
|
|
492
|
-
<IconPlayerPlay size={
|
|
555
|
+
<button className={`${styles.startBtn} ${styles.startBtnBig}`} onClick={handleStart} disabled={actionLoading}>
|
|
556
|
+
<IconPlayerPlay size={16} />
|
|
493
557
|
{actionLoading ? 'Starting…' : 'Start Router'}
|
|
494
558
|
</button>
|
|
495
559
|
) : (
|
|
@@ -504,34 +568,6 @@ export default function RouterView({ onClose, onToast }) {
|
|
|
504
568
|
</div>
|
|
505
569
|
</div>
|
|
506
570
|
|
|
507
|
-
{/* Quick Setup */}
|
|
508
|
-
{running && quickSetup && (
|
|
509
|
-
<div className={styles.quickSetup}>
|
|
510
|
-
<h3 className={styles.sectionTitle}>
|
|
511
|
-
<IconCopy size={14} />
|
|
512
|
-
Quick Setup
|
|
513
|
-
</h3>
|
|
514
|
-
<div className={styles.quickRows}>
|
|
515
|
-
{quickSetup.baseUrl && (
|
|
516
|
-
<div className={styles.quickRow}>
|
|
517
|
-
<span className={styles.quickLabel}>Base URL</span>
|
|
518
|
-
<code className={styles.quickValue}>{quickSetup.baseUrl}</code>
|
|
519
|
-
<button className={styles.copyBtn} onClick={() => handleCopy(quickSetup.baseUrl, 'url')}>
|
|
520
|
-
{copied === 'url' ? <IconCheck size={12} /> : <IconCopy size={12} />}
|
|
521
|
-
</button>
|
|
522
|
-
</div>
|
|
523
|
-
)}
|
|
524
|
-
<div className={styles.quickRow}>
|
|
525
|
-
<span className={styles.quickLabel}>Model</span>
|
|
526
|
-
<code className={styles.quickValue}>{quickSetup.model}</code>
|
|
527
|
-
<button className={styles.copyBtn} onClick={() => handleCopy(quickSetup.model, 'model')}>
|
|
528
|
-
{copied === 'model' ? <IconCheck size={12} /> : <IconCopy size={12} />}
|
|
529
|
-
</button>
|
|
530
|
-
</div>
|
|
531
|
-
</div>
|
|
532
|
-
</div>
|
|
533
|
-
)}
|
|
534
|
-
|
|
535
571
|
{/* Active Set Manager */}
|
|
536
572
|
{running && (
|
|
537
573
|
<div className={styles.section}>
|
|
@@ -580,20 +616,42 @@ export default function RouterView({ onClose, onToast }) {
|
|
|
580
616
|
|
|
581
617
|
{localModels.length === 0 ? (
|
|
582
618
|
<div className={styles.setEmpty}>
|
|
583
|
-
|
|
619
|
+
<div className={styles.setEmptyTitle}>No models in the active set</div>
|
|
620
|
+
<div className={styles.setEmptyHint}>
|
|
621
|
+
Add models from the picker below, or click <strong>Sync best</strong> above to auto-pick working models.
|
|
622
|
+
</div>
|
|
584
623
|
</div>
|
|
585
624
|
) : (
|
|
586
|
-
|
|
625
|
+
<>
|
|
626
|
+
{/* 📖 Priority legend — explains the fallback chain so users
|
|
627
|
+
understand WHY the top model serves every request (issue #120).
|
|
628
|
+
Higher priority = tried first; the rest are failover targets. */}
|
|
629
|
+
<div className={styles.priorityLegend}>
|
|
630
|
+
<span className={styles.legendPrimary}><IconArrowRight size={11} /> Primary</span>
|
|
631
|
+
<span className={styles.legendSeparator}>tries first</span>
|
|
632
|
+
<span className={styles.legendFallback}>Fallback</span>
|
|
633
|
+
<span className={styles.legendSeparator}>on failure / rate-limit</span>
|
|
634
|
+
{running && nextToServeKey && (
|
|
635
|
+
<span className={styles.legendNext}>Next up: <code>{nextToServeKey}</code></span>
|
|
636
|
+
)}
|
|
637
|
+
</div>
|
|
638
|
+
<div className={styles.setList} onDragLeave={handleDragLeave}>
|
|
587
639
|
{localModels.map((m, idx) => {
|
|
588
640
|
const key = `${m.provider}/${m.model}`
|
|
589
641
|
const cb = circuitBreakers[key] || {}
|
|
590
642
|
const isDragging = draggingKey === key
|
|
591
643
|
const dropAbove = dropPosition?.key === key && dropPosition.side === 'above'
|
|
592
644
|
const dropBelow = dropPosition?.key === key && dropPosition.side === 'below'
|
|
645
|
+
// 📖 Priority semantics for the UI: #1 is the Primary model the
|
|
646
|
+
// 📖 router tries first; everyone else is a Fallback. `isNext` is
|
|
647
|
+
// 📖 true for the exact model the daemon will serve next request —
|
|
648
|
+
// 📖 derived from /stats.routingOrder (priority-first, see #120).
|
|
649
|
+
const isPrimary = idx === 0
|
|
650
|
+
const isNext = running && nextToServeKey === key
|
|
593
651
|
return (
|
|
594
652
|
<div
|
|
595
653
|
key={key}
|
|
596
|
-
className={`${styles.setRow} ${isDragging ? styles.setRowDragging : ''} ${dropAbove ? `${styles.setRowDropTarget} ${styles.setRowDropTargetAbove}` : ''} ${dropBelow ? `${styles.setRowDropTarget} ${styles.setRowDropTargetBelow}` : ''}`}
|
|
654
|
+
className={`${styles.setRow} ${isDragging ? styles.setRowDragging : ''} ${isNext ? styles.setRowNext : ''} ${dropAbove ? `${styles.setRowDropTarget} ${styles.setRowDropTargetAbove}` : ''} ${dropBelow ? `${styles.setRowDropTarget} ${styles.setRowDropTargetBelow}` : ''}`}
|
|
597
655
|
draggable
|
|
598
656
|
onDragStart={(e) => handleDragStart(e, idx)}
|
|
599
657
|
onDragOver={(e) => handleDragOver(e, idx)}
|
|
@@ -604,7 +662,12 @@ export default function RouterView({ onClose, onToast }) {
|
|
|
604
662
|
<span className={styles.setDragHandle} aria-hidden>
|
|
605
663
|
<IconGripVertical size={14} />
|
|
606
664
|
</span>
|
|
607
|
-
<span
|
|
665
|
+
<span
|
|
666
|
+
className={`${styles.setPriority} ${isPrimary ? styles.setPriorityPrimary : styles.setPriorityFallback}`}
|
|
667
|
+
title={isPrimary ? 'Primary model — tried first on every request' : `Fallback #${idx + 1} — used when higher-priority models fail or rate-limit`}
|
|
668
|
+
>
|
|
669
|
+
{isPrimary ? 'Primary' : `#${idx + 1}`}
|
|
670
|
+
</span>
|
|
608
671
|
<span className={styles.setKey}>{key}</span>
|
|
609
672
|
{m.tier && <span className={styles.setTier}>{m.tier}</span>}
|
|
610
673
|
<CircuitBadge state={cb.state || m.state} />
|
|
@@ -641,6 +704,7 @@ export default function RouterView({ onClose, onToast }) {
|
|
|
641
704
|
)
|
|
642
705
|
})}
|
|
643
706
|
</div>
|
|
707
|
+
</>
|
|
644
708
|
)}
|
|
645
709
|
|
|
646
710
|
{pickerOpen && (
|
|
@@ -720,8 +784,8 @@ export default function RouterView({ onClose, onToast }) {
|
|
|
720
784
|
</div>
|
|
721
785
|
)}
|
|
722
786
|
|
|
723
|
-
{/* Request Log */}
|
|
724
|
-
{running &&
|
|
787
|
+
{/* Request Log — always visible when running */}
|
|
788
|
+
{running && (
|
|
725
789
|
<div className={styles.section}>
|
|
726
790
|
<h3 className={styles.sectionTitle} onClick={() => setLogExpanded(!logExpanded)} style={{ cursor: 'pointer' }}>
|
|
727
791
|
<IconActivity size={14} />
|
|
@@ -729,23 +793,27 @@ export default function RouterView({ onClose, onToast }) {
|
|
|
729
793
|
{logExpanded ? <IconChevronUp size={12} /> : <IconChevronDown size={12} />}
|
|
730
794
|
</h3>
|
|
731
795
|
{logExpanded && (
|
|
732
|
-
|
|
733
|
-
{
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
796
|
+
requestLog.length === 0 ? (
|
|
797
|
+
<div className={styles.logEmpty}>No requests yet. Start coding to see traffic here.</div>
|
|
798
|
+
) : (
|
|
799
|
+
<div className={styles.logList}>
|
|
800
|
+
{requestLog.map((entry, i) => (
|
|
801
|
+
<div key={i} className={styles.logRow}>
|
|
802
|
+
<span className={entry.error ? styles.logErr : styles.logOk}>
|
|
803
|
+
{entry.status || '—'}
|
|
804
|
+
</span>
|
|
805
|
+
<span className={styles.logModel}>{entry.model}</span>
|
|
806
|
+
<span className={styles.logLatency}>
|
|
807
|
+
{entry.latency_ms != null ? `${entry.latency_ms}ms` : '—'}
|
|
808
|
+
</span>
|
|
809
|
+
<span className={styles.logTokens}>
|
|
810
|
+
{entry.tokens > 0 ? formatNumber(entry.tokens) + ' tok' : ''}
|
|
811
|
+
</span>
|
|
812
|
+
{entry.failover && <span className={styles.logFailover}>failover</span>}
|
|
813
|
+
</div>
|
|
814
|
+
))}
|
|
815
|
+
</div>
|
|
816
|
+
)
|
|
749
817
|
)}
|
|
750
818
|
</div>
|
|
751
819
|
)}
|
|
@@ -140,6 +140,13 @@
|
|
|
140
140
|
&:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
/* 📖 Bigger start button for the stopped state — more prominent CTA. */
|
|
144
|
+
.startBtnBig {
|
|
145
|
+
padding: 10px 24px;
|
|
146
|
+
font-size: 15px;
|
|
147
|
+
border-radius: 10px;
|
|
148
|
+
}
|
|
149
|
+
|
|
143
150
|
.stopBtn {
|
|
144
151
|
display: flex;
|
|
145
152
|
align-items: center;
|
|
@@ -175,6 +182,33 @@
|
|
|
175
182
|
margin-bottom: 16px;
|
|
176
183
|
}
|
|
177
184
|
|
|
185
|
+
/* 📖 Hero glow when running — makes the quick setup card visually prominent. */
|
|
186
|
+
.quickSetupHero {
|
|
187
|
+
border: 1px solid rgba(0, 200, 100, 0.25);
|
|
188
|
+
box-shadow: 0 0 16px rgba(0, 200, 100, 0.08);
|
|
189
|
+
border-radius: 10px;
|
|
190
|
+
padding: 12px;
|
|
191
|
+
background: linear-gradient(135deg, rgba(0, 200, 100, 0.05), transparent);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/* 📖 "Copy all" button — sits at the right of the Quick Setup title. */
|
|
195
|
+
.copyAllBtn {
|
|
196
|
+
display: inline-flex;
|
|
197
|
+
align-items: center;
|
|
198
|
+
gap: 4px;
|
|
199
|
+
margin-left: auto;
|
|
200
|
+
padding: 3px 10px;
|
|
201
|
+
background: var(--bg-secondary, #14141f);
|
|
202
|
+
border: 1px solid var(--border, #1e1e2e);
|
|
203
|
+
border-radius: 6px;
|
|
204
|
+
color: var(--text-secondary, #aaa);
|
|
205
|
+
font-size: 11px;
|
|
206
|
+
font-weight: 500;
|
|
207
|
+
cursor: pointer;
|
|
208
|
+
font-family: inherit;
|
|
209
|
+
&:hover { color: var(--text-primary, #e0e0e0); border-color: var(--accent, #00c864); }
|
|
210
|
+
}
|
|
211
|
+
|
|
178
212
|
.sectionTitle {
|
|
179
213
|
display: flex;
|
|
180
214
|
align-items: center;
|
|
@@ -351,6 +385,16 @@
|
|
|
351
385
|
text-transform: uppercase;
|
|
352
386
|
}
|
|
353
387
|
|
|
388
|
+
.logEmpty {
|
|
389
|
+
padding: 16px;
|
|
390
|
+
text-align: center;
|
|
391
|
+
color: var(--text-muted, #888);
|
|
392
|
+
font-size: 12px;
|
|
393
|
+
background: var(--bg-secondary, #14141f);
|
|
394
|
+
border: 1px dashed var(--border, #1e1e2e);
|
|
395
|
+
border-radius: 8px;
|
|
396
|
+
}
|
|
397
|
+
|
|
354
398
|
@keyframes fadeIn {
|
|
355
399
|
from { opacity: 0; }
|
|
356
400
|
to { opacity: 1; }
|
|
@@ -421,7 +465,7 @@
|
|
|
421
465
|
}
|
|
422
466
|
|
|
423
467
|
.setEmpty {
|
|
424
|
-
padding: 16px;
|
|
468
|
+
padding: 20px 16px;
|
|
425
469
|
text-align: center;
|
|
426
470
|
color: var(--text-muted, #888);
|
|
427
471
|
font-size: 12px;
|
|
@@ -430,6 +474,23 @@
|
|
|
430
474
|
border-radius: 8px;
|
|
431
475
|
}
|
|
432
476
|
|
|
477
|
+
.setEmptyTitle {
|
|
478
|
+
font-size: 13px;
|
|
479
|
+
font-weight: 600;
|
|
480
|
+
color: var(--text-secondary, #aaa);
|
|
481
|
+
margin-bottom: 4px;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
.setEmptyHint {
|
|
485
|
+
font-size: 11px;
|
|
486
|
+
color: var(--text-muted, #888);
|
|
487
|
+
line-height: 1.5;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
.setEmptyHint strong {
|
|
491
|
+
color: var(--text-secondary, #aaa);
|
|
492
|
+
}
|
|
493
|
+
|
|
433
494
|
.setRow {
|
|
434
495
|
display: flex;
|
|
435
496
|
align-items: center;
|
|
@@ -510,6 +571,79 @@
|
|
|
510
571
|
text-transform: uppercase;
|
|
511
572
|
}
|
|
512
573
|
|
|
574
|
+
/* 📖 Priority badge variants — make the fallback chain semantics explicit
|
|
575
|
+
(issue #120). Primary gets the accent color so users instantly see which
|
|
576
|
+
model serves every request; fallbacks stay muted. */
|
|
577
|
+
.setPriorityPrimary {
|
|
578
|
+
min-width: 52px;
|
|
579
|
+
padding: 2px 8px;
|
|
580
|
+
border-radius: 4px;
|
|
581
|
+
background: rgba(0, 200, 100, 0.14);
|
|
582
|
+
color: var(--accent, #00c864);
|
|
583
|
+
border: 1px solid rgba(0, 200, 100, 0.3);
|
|
584
|
+
text-transform: uppercase;
|
|
585
|
+
letter-spacing: 0.3px;
|
|
586
|
+
text-align: center;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
.setPriorityFallback {
|
|
590
|
+
text-transform: none;
|
|
591
|
+
opacity: 0.85;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/* 📖 Next-to-serve row — subtle accent left border + tint so the user can
|
|
595
|
+
see at a glance which model will handle the next chat completion.
|
|
596
|
+
Only applied when the daemon is running and routingOrder is known. */
|
|
597
|
+
.setRowNext {
|
|
598
|
+
background: linear-gradient(90deg, rgba(0, 200, 100, 0.07), transparent 60%);
|
|
599
|
+
border-left: 2px solid var(--accent, #00c864);
|
|
600
|
+
padding-left: 8px;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/* 📖 Priority legend — one-line explainer above the set list. */
|
|
604
|
+
.priorityLegend {
|
|
605
|
+
display: flex;
|
|
606
|
+
align-items: center;
|
|
607
|
+
gap: 8px;
|
|
608
|
+
flex-wrap: wrap;
|
|
609
|
+
padding: 6px 10px;
|
|
610
|
+
margin-bottom: 8px;
|
|
611
|
+
font-size: 11px;
|
|
612
|
+
color: var(--text-muted, #888);
|
|
613
|
+
background: var(--bg-secondary, #14141f);
|
|
614
|
+
border: 1px solid var(--border, #1e1e2e);
|
|
615
|
+
border-radius: 6px;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.legendPrimary {
|
|
619
|
+
display: inline-flex;
|
|
620
|
+
align-items: center;
|
|
621
|
+
gap: 3px;
|
|
622
|
+
font-weight: 600;
|
|
623
|
+
color: var(--accent, #00c864);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
.legendFallback {
|
|
627
|
+
font-weight: 600;
|
|
628
|
+
color: var(--text-secondary, #aaa);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
.legendSeparator {
|
|
632
|
+
color: var(--text-muted, #888);
|
|
633
|
+
opacity: 0.7;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
.legendNext {
|
|
637
|
+
margin-left: auto;
|
|
638
|
+
color: var(--text-secondary, #aaa);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
.legendNext code {
|
|
642
|
+
color: var(--accent, #00c864);
|
|
643
|
+
font-family: 'SF Mono', monospace;
|
|
644
|
+
font-size: 10px;
|
|
645
|
+
}
|
|
646
|
+
|
|
513
647
|
.setRowBtns {
|
|
514
648
|
display: flex;
|
|
515
649
|
gap: 2px;
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file src/product-flags.js
|
|
3
|
-
* @description Product-level flags and feature gates.
|
|
4
|
-
*
|
|
5
|
-
* @details
|
|
6
|
-
* 📖 Previously held PROXY_DISABLED_NOTICE for the proxy bridge rebuild.
|
|
7
|
-
* 📖 That notice was removed in 0.3.22 after the proxy surface was fully
|
|
8
|
-
* 📖 retired. File kept as a home for future product-level flags.
|
|
9
|
-
*/
|