free-coding-models 0.5.4 → 0.5.6

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.
Files changed (33) hide show
  1. package/README.md +8 -5
  2. package/bin/free-coding-models.js +29 -8
  3. package/changelog/v0.5.5.md +16 -0
  4. package/changelog/v0.5.6.md +24 -0
  5. package/package.json +4 -4
  6. package/src/core/changelog-loader.js +5 -1
  7. package/src/core/router-daemon.js +11 -0
  8. package/src/core/updater.js +174 -10
  9. package/src/tui/app.js +11 -31
  10. package/src/tui/render-table.js +9 -3
  11. package/src/tui/tui-state.js +6 -0
  12. package/web/dist/assets/index-Blp9QJev.js +39 -0
  13. package/web/dist/assets/{index-BrpHevg4.css → index-Cz_aCLTR.css} +1 -1
  14. package/web/dist/index.html +2 -2
  15. package/web/server.js +158 -2
  16. package/web/src/App.jsx +107 -58
  17. package/web/src/components/changelog/ChangelogView.jsx +135 -0
  18. package/web/src/components/changelog/ChangelogView.module.css +160 -0
  19. package/web/src/components/help/HelpView.jsx +188 -0
  20. package/web/src/components/help/HelpView.module.css +157 -0
  21. package/web/src/components/layout/Header.jsx +8 -3
  22. package/web/src/components/palette/CommandPalette.jsx +228 -74
  23. package/web/src/components/settings/SettingsView.jsx +281 -8
  24. package/web/src/components/settings/SettingsView.module.css +174 -0
  25. package/web/src/components/update/UpdateChip.jsx +104 -0
  26. package/web/src/components/update/UpdateChip.module.css +146 -0
  27. package/web/src/global.css +15 -0
  28. package/web/src/hooks/urlState.constants.js +25 -0
  29. package/web/src/hooks/useChangelog.js +51 -0
  30. package/web/src/hooks/useSocket.js +3 -0
  31. package/web/src/hooks/useUpdateChecker.js +91 -0
  32. package/web/src/hooks/useUrlState.js +122 -62
  33. package/web/dist/assets/index-BoWmUveV.js +0 -39
@@ -0,0 +1,188 @@
1
+ /**
2
+ * @file web/src/components/help/HelpView.jsx
3
+ * @description Help modal — M2 parity with the TUI's `K` / `I` help overlay.
4
+ * 📖 Renders the same content the TUI help overlay does, as JSX, with a live
5
+ * 📖 search bar at the top. The TUI source-of-truth is `src/tui/cli-help.js`
6
+ * 📖 (CLI flags) and `src/tui/overlays.js` `renderHelp` (key bindings) — we
7
+ * 📖 re-render those lists statically here to avoid a TUI-engine import that
8
+ * 📖 would pull chalk into the Web bundle.
9
+ *
10
+ * 📖 The header's overflow menu and the ⌘K palette expose "Help" as a page;
11
+ * 📖 this component is rendered as a full-screen modal inside the Web.
12
+ *
13
+ * @functions
14
+ * → HelpView → main modal component
15
+ */
16
+ import { useState, useMemo } from 'react'
17
+ import { IconSearch, IconX } from '@tabler/icons-react'
18
+ import styles from './HelpView.module.css'
19
+
20
+ const SECTIONS = [
21
+ {
22
+ id: 'navigation',
23
+ title: '🧭 Navigation',
24
+ items: [
25
+ { key: '↑ / ↓', desc: 'Navigate rows in the main table (planned — currently mouse-only)' },
26
+ { key: 'Enter', desc: 'Open the detail panel for the focused row' },
27
+ { key: 'Esc', desc: 'Close any open modal or detail panel' },
28
+ ],
29
+ },
30
+ {
31
+ id: 'filters',
32
+ title: '🔍 Filters',
33
+ items: [
34
+ { key: 'Tier chip', desc: 'Click to cycle: All → S+ → S → A+ → A → A- → B+ → B → C → All' },
35
+ { key: 'Status chip', desc: 'Click to cycle: All → Up → Down → Pending' },
36
+ { key: 'Verdict chip', desc: 'Click to cycle: All → Perfect → Normal → Spiky → Slow → Overloaded → Down → Unstable → Pending' },
37
+ { key: 'Health chip', desc: 'Click to cycle: All → Up → Timeout → Down → Pending → No key → Auth err' },
38
+ { key: 'Visibility', desc: 'Normal (all) / Configured only (hide no-key) / Usable only (UP + good verdict)' },
39
+ { key: 'Provider dropdown', desc: 'Filter by provider' },
40
+ { key: 'Custom text filter', desc: 'Apply via ⌘K palette or `Apply text filter` — clear with the X chip' },
41
+ { key: 'Reset button', desc: 'Clears every active filter + sort back to defaults' },
42
+ ],
43
+ },
44
+ {
45
+ id: 'sort',
46
+ title: '📶 Sort',
47
+ items: [
48
+ { key: 'Click any column header', desc: 'Sort asc → desc → reset (no sort, default order)' },
49
+ { key: 'Resizable columns', desc: 'Drag the right edge of any header to resize. Double-click resets one column.' },
50
+ ],
51
+ },
52
+ {
53
+ id: 'favorites',
54
+ title: '⭐ Favorites',
55
+ items: [
56
+ { key: 'Star button (per row)', desc: 'Click to favorite / unfavorite the model (TUI: F key)' },
57
+ { key: 'Detail panel', desc: 'Favorite + Up/Down reorder + current priority rank (TUI: Shift+↑/↓)' },
58
+ { key: 'Pinned mode', desc: 'In the Settings view, toggle "Pinned + always visible" (TUI: Y key)' },
59
+ { key: 'Shared with TUI', desc: 'Favorites are persisted in the same ~/.free-coding-models.json the TUI uses' },
60
+ ],
61
+ },
62
+ {
63
+ id: 'benchmark',
64
+ title: '🤖 AI Speed Test (benchmark)',
65
+ items: [
66
+ { key: 'Header button', desc: 'Run a global AI Speed Test on every visible model' },
67
+ { key: 'AI Lat. cell', desc: 'Click any AI Lat. cell to benchmark just that model (TUI: Ctrl+A)' },
68
+ { key: 'Detail panel', desc: 'Dedicated "AI Speed Test" button (TUI: Ctrl+A)' },
69
+ { key: 'TPS column', desc: 'Tokens per second for the model — appears after a benchmark run' },
70
+ { key: 'Latency column', desc: 'Real completion latency (not just ping) for the model' },
71
+ ],
72
+ },
73
+ {
74
+ id: 'tools',
75
+ title: '🧰 Tool mode (M3)',
76
+ items: [
77
+ { key: 'Coming in M3', desc: 'Tool mode picker, per-row Launch button, missing-tool install prompt, incompatible-fallback modal' },
78
+ ],
79
+ },
80
+ {
81
+ id: 'palette',
82
+ title: '⚡ Command palette',
83
+ items: [
84
+ { key: '⌘K / Ctrl+P', desc: 'The Web\'s only global keyboard shortcut — toggles the command palette' },
85
+ { key: 'Type to search', desc: 'Fuzzy match across all filters, sorts, tools, pages, and theme / ping / reset actions' },
86
+ { key: '↑ / ↓ + Enter', desc: 'Navigate the results and execute the highlighted command' },
87
+ { key: 'Esc', desc: 'Close the palette without running anything' },
88
+ ],
89
+ },
90
+ {
91
+ id: 'theme',
92
+ title: '🌗 Theme',
93
+ items: [
94
+ { key: 'Theme button (header)', desc: 'Click to cycle: auto → dark → light (TUI: G key)' },
95
+ { key: 'Auto mode', desc: 'Follows the OS prefers-color-scheme preference and updates live' },
96
+ ],
97
+ },
98
+ {
99
+ id: 'ping',
100
+ title: '⚡ Ping mode',
101
+ items: [
102
+ { key: 'Speed / Normal / Slow / Forced', desc: 'Ping cadence (2s / 10s / 30s / 4s) — TUI: W key' },
103
+ { key: 'next ping in Xs', desc: 'Live countdown shown in the FilterBar (TUI footer style)' },
104
+ ],
105
+ },
106
+ {
107
+ id: 'url',
108
+ title: '🔗 URL deep-linking',
109
+ items: [
110
+ { key: '?tier=S+&sort=verdict&origin=groq&view=dashboard', desc: 'Every filter / sort / view is reflected in the URL — share pre-filtered links' },
111
+ ],
112
+ },
113
+ {
114
+ id: 'cli',
115
+ title: '⌨️ CLI parity',
116
+ items: [
117
+ { key: 'Same storage', desc: 'The Web reads + writes the same ~/.free-coding-models.json as the TUI' },
118
+ { key: 'Same engine', desc: 'All model parsing, ping, and benchmark code is shared with the TUI' },
119
+ ],
120
+ },
121
+ ]
122
+
123
+ export default function HelpView({ onClose }) {
124
+ const [query, setQuery] = useState('')
125
+
126
+ // 📖 Filter every section's items by the live query. Case-insensitive
127
+ // 📖 substring match on the key or description. Empty results hide the
128
+ // 📖 section entirely.
129
+ const filteredSections = useMemo(() => {
130
+ const q = query.trim().toLowerCase()
131
+ if (!q) return SECTIONS
132
+ return SECTIONS.map((section) => ({
133
+ ...section,
134
+ items: section.items.filter(
135
+ (item) => item.key.toLowerCase().includes(q) || item.desc.toLowerCase().includes(q),
136
+ ),
137
+ })).filter((section) => section.items.length > 0)
138
+ }, [query])
139
+
140
+ return (
141
+ <div className={styles.backdrop} onClick={onClose}>
142
+ <div className={styles.modal} onClick={(e) => e.stopPropagation()}>
143
+ <div className={styles.header}>
144
+ <div className={styles.titleRow}>
145
+ <h2 className={styles.title}>❓ Help & Keyboard Shortcuts</h2>
146
+ <button className={styles.closeBtn} onClick={onClose} aria-label="Close help">
147
+ <IconX size={18} stroke={1.5} />
148
+ </button>
149
+ </div>
150
+ <div className={styles.searchBar}>
151
+ <IconSearch size={14} stroke={1.5} className={styles.searchIcon} />
152
+ <input
153
+ className={styles.searchInput}
154
+ placeholder="Search help (filters, hotkeys, TUI parity)…"
155
+ value={query}
156
+ onChange={(e) => setQuery(e.target.value)}
157
+ autoFocus
158
+ />
159
+ </div>
160
+ <p className={styles.note}>
161
+ The Web Dashboard is mouse-first. The only global keyboard shortcut is{' '}
162
+ <kbd>⌘K</kbd> / <kbd>Ctrl+P</kbd> for the command palette — everything else
163
+ lives here, in the header menu, or in modals.
164
+ </p>
165
+ </div>
166
+ <div className={styles.body}>
167
+ {filteredSections.length === 0 ? (
168
+ <div className={styles.empty}>No help matches "{query}".</div>
169
+ ) : (
170
+ filteredSections.map((section) => (
171
+ <section key={section.id} className={styles.section}>
172
+ <h3 className={styles.sectionTitle}>{section.title}</h3>
173
+ <ul className={styles.itemList}>
174
+ {section.items.map((item) => (
175
+ <li key={item.key + item.desc} className={styles.item}>
176
+ <span className={styles.itemKey}>{item.key}</span>
177
+ <span className={styles.itemDesc}>{item.desc}</span>
178
+ </li>
179
+ ))}
180
+ </ul>
181
+ </section>
182
+ ))
183
+ )}
184
+ </div>
185
+ </div>
186
+ </div>
187
+ )
188
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * @file web/src/components/help/HelpView.module.css
3
+ * @description Full-screen help modal styles — TUI parity.
4
+ */
5
+
6
+ .backdrop {
7
+ position: fixed;
8
+ inset: 0;
9
+ background: rgba(0, 0, 0, 0.55);
10
+ backdrop-filter: blur(4px);
11
+ -webkit-backdrop-filter: blur(4px);
12
+ z-index: 500;
13
+ display: flex;
14
+ align-items: center;
15
+ justify-content: center;
16
+ padding: 5vh 4vw;
17
+ }
18
+
19
+ .modal {
20
+ width: min(820px, 95vw);
21
+ max-height: 90vh;
22
+ background: var(--color-bg-elevated);
23
+ border: 1px solid var(--color-border-hover);
24
+ border-radius: 14px;
25
+ box-shadow: 0 24px 64px rgba(0, 0, 0, 0.5);
26
+ display: flex;
27
+ flex-direction: column;
28
+ overflow: hidden;
29
+ }
30
+
31
+ .header {
32
+ padding: 20px 24px 16px;
33
+ border-bottom: 1px solid var(--color-border);
34
+ }
35
+
36
+ .titleRow {
37
+ display: flex;
38
+ align-items: center;
39
+ justify-content: space-between;
40
+ gap: 12px;
41
+ }
42
+
43
+ .title {
44
+ font-size: 17px;
45
+ font-weight: 700;
46
+ margin: 0;
47
+ color: var(--color-text);
48
+ }
49
+
50
+ .closeBtn {
51
+ background: transparent;
52
+ border: none;
53
+ color: var(--color-text-muted);
54
+ cursor: pointer;
55
+ padding: 4px;
56
+ border-radius: 4px;
57
+ display: inline-flex;
58
+ align-items: center;
59
+ justify-content: center;
60
+ }
61
+ .closeBtn:hover { color: var(--color-text); background: var(--color-bg-hover); }
62
+
63
+ .searchBar {
64
+ display: flex;
65
+ align-items: center;
66
+ gap: 8px;
67
+ margin-top: 12px;
68
+ background: var(--color-surface);
69
+ border: 1px solid var(--color-border);
70
+ border-radius: 8px;
71
+ padding: 0 12px;
72
+ height: 34px;
73
+ }
74
+ .searchBar:focus-within {
75
+ border-color: var(--color-accent);
76
+ box-shadow: 0 0 0 3px var(--color-accent-glow);
77
+ }
78
+ .searchIcon { color: var(--color-text-dim); }
79
+ .searchInput {
80
+ flex: 1;
81
+ background: none;
82
+ border: none;
83
+ outline: none;
84
+ color: var(--color-text);
85
+ font-size: 13px;
86
+ font-family: var(--font-sans);
87
+ }
88
+ .searchInput::placeholder { color: var(--color-text-dim); }
89
+
90
+ .note {
91
+ margin: 10px 0 0;
92
+ font-size: 11px;
93
+ color: var(--color-text-muted);
94
+ line-height: 1.5;
95
+ }
96
+ .note kbd {
97
+ display: inline-block;
98
+ font-family: var(--font-mono);
99
+ background: var(--color-surface);
100
+ border: 1px solid var(--color-border);
101
+ padding: 1px 5px;
102
+ border-radius: 3px;
103
+ font-size: 10px;
104
+ }
105
+
106
+ .body {
107
+ padding: 18px 24px 24px;
108
+ overflow-y: auto;
109
+ flex: 1;
110
+ }
111
+
112
+ .section { margin-bottom: 18px; }
113
+ .sectionTitle {
114
+ font-size: 12px;
115
+ font-weight: 700;
116
+ text-transform: uppercase;
117
+ letter-spacing: 0.6px;
118
+ color: var(--color-text-muted);
119
+ margin: 0 0 8px;
120
+ }
121
+
122
+ .itemList {
123
+ list-style: none;
124
+ margin: 0;
125
+ padding: 0;
126
+ display: flex;
127
+ flex-direction: column;
128
+ gap: 4px;
129
+ }
130
+ .item {
131
+ display: grid;
132
+ grid-template-columns: minmax(160px, 0.4fr) 1fr;
133
+ gap: 12px;
134
+ align-items: baseline;
135
+ padding: 6px 10px;
136
+ background: var(--color-surface);
137
+ border: 1px solid var(--color-border);
138
+ border-radius: 6px;
139
+ font-size: 12px;
140
+ }
141
+ .itemKey {
142
+ font-weight: 600;
143
+ color: var(--color-text);
144
+ font-family: var(--font-mono);
145
+ font-size: 11px;
146
+ }
147
+ .itemDesc {
148
+ color: var(--color-text-muted);
149
+ line-height: 1.5;
150
+ }
151
+
152
+ .empty {
153
+ text-align: center;
154
+ padding: 40px 20px;
155
+ color: var(--color-text-dim);
156
+ font-size: 13px;
157
+ }
@@ -28,10 +28,11 @@ const NAV_ITEMS = [
28
28
  { id: 'router', label: 'Router', icon: IconRoute, comingIn: 'M4' },
29
29
  ]
30
30
 
31
- // 📖 Overflow menu items — Help, Changelog, Install Endpoints, Installed Models.
31
+ // 📖 Overflow menu items — Help + Changelog shipped in M2; Install Endpoints
32
+ // 📖 and Installed Models are still M4.
32
33
  const MENU_ITEMS = [
33
- { id: 'help', label: 'Help', icon: IconQuestionMark, comingIn: 'M2' },
34
- { id: 'changelog', label: 'Changelog', icon: IconHistory, comingIn: 'M2' },
34
+ { id: 'help', label: 'Help', icon: IconQuestionMark },
35
+ { id: 'changelog', label: 'Changelog', icon: IconHistory },
35
36
  { id: 'install-endpoints', label: 'Install Endpoints', icon: IconPlug, comingIn: 'M4' },
36
37
  { id: 'installed-models', label: 'Installed Models', icon: IconFolders, comingIn: 'M4' },
37
38
  ]
@@ -42,6 +43,7 @@ export default function Header({
42
43
  onToggleTheme, onOpenExport, onOpenCommandPalette,
43
44
  onBenchmark, benchmarkRunning, benchmarkTotal, benchmarkCompleted,
44
45
  modelsCount, theme, onToast,
46
+ updateSlot = null,
45
47
  }) {
46
48
  const [menuOpen, setMenuOpen] = useState(false)
47
49
  const menuRef = useRef(null)
@@ -188,6 +190,9 @@ export default function Header({
188
190
  )}
189
191
  </button>
190
192
 
193
+ {/* 📖 M2: update chip slot. Hidden when no update is available. */}
194
+ {updateSlot}
195
+
191
196
  <button className={styles.iconBtn} onClick={onToggleTheme} title={`Theme: ${theme} (click to cycle auto / dark / light)`}>
192
197
  {theme === 'light' ? <IconMoon size={16} stroke={1.5} /> : <IconSun size={16} stroke={1.5} />}
193
198
  </button>