neoagent 2.4.2-beta.0 → 2.4.2-beta.2

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.
@@ -819,6 +819,13 @@
819
819
  Providers
820
820
  </button>
821
821
 
822
+ <button class="nav-item" data-page="models" onclick="showPage('models',this)">
823
+ <svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
824
+ <path fill-rule="evenodd" d="M2 10a8 8 0 1116 0 8 8 0 01-16 0zm8-6a6 6 0 100 12 6 6 0 000-12zm-3 7a1 1 0 011-1h4a1 1 0 011 1v1a1 1 0 01-1 1H8a1 1 0 01-1-1v-1z" clip-rule="evenodd"/>
825
+ </svg>
826
+ Models
827
+ </button>
828
+
822
829
  <button class="nav-item" data-page="config" onclick="showPage('config',this)">
823
830
  <svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
824
831
  <path fill-rule="evenodd" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd"/>
@@ -828,11 +835,6 @@
828
835
  </nav>
829
836
 
830
837
  <div class="sidebar-footer">
831
- <div class="sidebar-legal">
832
- <a href="/legal.html#overview" target="_blank" rel="noopener">Privacy Policy</a>
833
- <span>·</span>
834
- <a href="/legal.html#legal-notice" target="_blank" rel="noopener">Legal Notice</a>
835
- </div>
836
838
  <button class="nav-item btn-danger" onclick="signOut()">
837
839
  <svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
838
840
  <path fill-rule="evenodd" d="M3 3a1 1 0 00-1 1v12a1 1 0 102 0V4a1 1 0 00-1-1zm10.293 9.293a1 1 0 001.414 1.414l3-3a1 1 0 000-1.414l-3-3a1 1 0 10-1.414 1.414L14.586 9H7a1 1 0 100 2h7.586l-1.293 1.293z" clip-rule="evenodd"/>
@@ -988,6 +990,24 @@
988
990
  </div>
989
991
  </section>
990
992
 
993
+ <!-- Models -->
994
+ <section id="page-models" class="page" aria-label="Models">
995
+ <div class="page-header">
996
+ <div>
997
+ <h1 class="page-title">AI Models</h1>
998
+ <p class="page-subtitle">Enable or disable specific AI models globally for all users.</p>
999
+ </div>
1000
+ </div>
1001
+ <div class="content">
1002
+ <div class="card">
1003
+ <div class="toolbar" style="margin-bottom:14px; justify-content: flex-end;">
1004
+ <button class="btn btn-primary" onclick="saveEnabledModels(this)">Save Model Configuration</button>
1005
+ </div>
1006
+ <div id="models-content"><div class="empty"><span class="spinner"></span></div></div>
1007
+ </div>
1008
+ </div>
1009
+ </section>
1010
+
991
1011
  <!-- Configuration -->
992
1012
  <section id="page-config" class="page" aria-label="Configuration">
993
1013
  <div class="page-header">
@@ -71,6 +71,10 @@ function renderUsersTable(el, users) {
71
71
  <td style="font-family:var(--font-mono);font-size:12px;">${fmtBytes(u.storage_bytes)}</td>
72
72
  <td>
73
73
  <div style="display:flex;gap:6px;justify-content:flex-end;">
74
+ <button class="btn btn-ghost" style="padding:5px 10px;font-size:11px;"
75
+ onclick="editRateLimits('${esc(u.id)}','${esc(u.username)}')" title="Edit Rate Limits">
76
+ Limits
77
+ </button>
74
78
  <button class="btn btn-ghost" style="padding:5px 10px;font-size:11px;"
75
79
  onclick="forceLogout('${esc(u.id)}','${esc(u.username)}',this)" title="Revoke all sessions">
76
80
  Logout
@@ -145,3 +149,72 @@ function fmtDate(iso) {
145
149
  return new Date(iso).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
146
150
  } catch { return iso; }
147
151
  }
152
+
153
+ async function editRateLimits(id, username) {
154
+ try {
155
+ const res = await api(`/admin/api/users/${id}/rate-limits`).then(r => r.json());
156
+ const limits = res.limits || {};
157
+ const limit4h = limits.rate_limit_4h || '';
158
+ const limitWeekly = limits.rate_limit_weekly || '';
159
+
160
+ const overlay = document.createElement('div');
161
+ overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;z-index:9999;backdrop-filter:blur(2px);';
162
+ const modal = document.createElement('div');
163
+ modal.className = 'card';
164
+ modal.style.cssText = 'width:420px;background:var(--bg-primary);box-shadow:0 10px 40px rgba(0,0,0,0.5);border:1px solid var(--border);border-radius:12px;padding:24px;';
165
+ modal.innerHTML = `
166
+ <div style="font-size:16px;font-weight:700;color:var(--text);margin-bottom:6px;">Rate Limits</div>
167
+ <div style="font-size:13px;color:var(--text-muted);margin-bottom:20px;">Configure token limits for <span style="color:var(--text);font-weight:600;">@${esc(username)}</span>.</div>
168
+
169
+ <div>
170
+ <label style="display:block;font-size:12px;font-weight:600;margin-bottom:8px;color:var(--text);text-transform:uppercase;letter-spacing:0.05em;">4-Hour Limit (Tokens)</label>
171
+ <input type="number" min="0" step="1" id="limit-4h" value="${limit4h}" placeholder="e.g. 500000" style="width:100%;padding:10px 12px;margin-bottom:6px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text);">
172
+ <div style="font-size:11px;color:var(--text-muted);margin-bottom:20px;">Leave empty for no limit</div>
173
+
174
+ <label style="display:block;font-size:12px;font-weight:600;margin-bottom:8px;color:var(--text);text-transform:uppercase;letter-spacing:0.05em;">Weekly Limit (Tokens)</label>
175
+ <input type="number" min="0" step="1" id="limit-weekly" value="${limitWeekly}" placeholder="e.g. 2000000" style="width:100%;padding:10px 12px;margin-bottom:6px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text);">
176
+ <div style="font-size:11px;color:var(--text-muted);margin-bottom:24px;">Leave empty for no limit</div>
177
+ </div>
178
+
179
+ <div style="display:flex;gap:12px;justify-content:flex-end;">
180
+ <button class="btn btn-ghost" id="btn-cancel" style="padding:8px 16px;">Cancel</button>
181
+ <button class="btn btn-primary" id="btn-save" style="padding:8px 16px;">Save Limits</button>
182
+ </div>
183
+ `;
184
+ overlay.appendChild(modal);
185
+ document.body.appendChild(overlay);
186
+
187
+ document.getElementById('btn-cancel').onclick = () => overlay.remove();
188
+ document.getElementById('btn-save').onclick = async () => {
189
+ const v4 = document.getElementById('limit-4h').value;
190
+ const vw = document.getElementById('limit-weekly').value;
191
+ const parseLimit = (v) => {
192
+ if (!v) return null;
193
+ const n = Number(v);
194
+ return Number.isInteger(n) && n >= 0 ? n : null;
195
+ };
196
+ const bSave = document.getElementById('btn-save');
197
+ bSave.disabled = true;
198
+ bSave.textContent = 'Saving...';
199
+ try {
200
+ const payload = {
201
+ rate_limit_4h: parseLimit(v4),
202
+ rate_limit_weekly: parseLimit(vw)
203
+ };
204
+ const putRes = await api(`/admin/api/users/${id}/rate-limits`, {
205
+ method: 'PUT',
206
+ headers: {'Content-Type': 'application/json'},
207
+ body: JSON.stringify(payload)
208
+ });
209
+ if (!putRes.ok) throw new Error('Save failed');
210
+ overlay.remove();
211
+ } catch (err) {
212
+ alert('Failed to save rate limits');
213
+ bSave.disabled = false;
214
+ bSave.textContent = 'Save Limits';
215
+ }
216
+ };
217
+ } catch (err) {
218
+ alert('Failed to fetch rate limits');
219
+ }
220
+ }
@@ -1862,6 +1862,23 @@ function migrateUsersDisplayName() {
1862
1862
  }
1863
1863
  migrateUsersDisplayName();
1864
1864
 
1865
+ function migrateUserRateLimits() {
1866
+ try {
1867
+ const columns = db.pragma('table_info(users)');
1868
+ const hasRateLimit4h = columns.some((c) => c.name === 'rate_limit_4h');
1869
+ if (!hasRateLimit4h) {
1870
+ db.exec('ALTER TABLE users ADD COLUMN rate_limit_4h INTEGER');
1871
+ }
1872
+ const hasRateLimitWeekly = columns.some((c) => c.name === 'rate_limit_weekly');
1873
+ if (!hasRateLimitWeekly) {
1874
+ db.exec('ALTER TABLE users ADD COLUMN rate_limit_weekly INTEGER');
1875
+ }
1876
+ } catch (err) {
1877
+ console.warn('Could not add rate_limit columns:', err.message);
1878
+ }
1879
+ }
1880
+ migrateUserRateLimits();
1881
+
1865
1882
  function migrateIngestionDocumentsConnectionId() {
1866
1883
  // connection_id was initially nullable; NULL breaks the UNIQUE dedup constraint.
1867
1884
  // Coerce any existing NULLs to 0 so the unique index works as intended.
@@ -80,7 +80,6 @@ function registerStaticRoutes(app) {
80
80
  // Landing page at /
81
81
  app.use(express.static(LANDING_DIR));
82
82
  app.get('/', (req, res) => res.sendFile(path.join(LANDING_DIR, 'index.html')));
83
- app.get('/legal.html', (req, res) => res.sendFile(path.join(LANDING_DIR, 'legal.html')));
84
83
  }
85
84
 
86
85
  function serveFlutterApp(req, res) {
@@ -1 +1 @@
1
- fffcffcece223938c04c1b5ec10b61c5
1
+ 69851219011d2a9c760c214e28b850a0
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"c416acfeb8126e097f758c664aaa3da929e27d
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "248735873" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "3511630357" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
41
41
  }
42
42
  });