remote-coder 0.4.1__py3-none-any.whl

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 (78) hide show
  1. app/__init__.py +3 -0
  2. app/admin/__init__.py +0 -0
  3. app/admin/advanced_settings.py +88 -0
  4. app/admin/database_browser.py +301 -0
  5. app/admin/router.py +528 -0
  6. app/admin/static/i18n.js +401 -0
  7. app/admin/static/icons/advanced.svg +8 -0
  8. app/admin/static/icons/database.svg +5 -0
  9. app/admin/static/icons/download.svg +3 -0
  10. app/admin/static/icons/home.svg +4 -0
  11. app/admin/static/icons/logs.svg +3 -0
  12. app/admin/static/icons/projects.svg +5 -0
  13. app/admin/static/summary.js +73 -0
  14. app/admin/templates/admin.html +511 -0
  15. app/admin/templates/advanced.html +635 -0
  16. app/admin/templates/database.html +880 -0
  17. app/admin/templates/logs.html +686 -0
  18. app/admin/templates/projects.html +878 -0
  19. app/ai/__init__.py +0 -0
  20. app/ai/base.py +129 -0
  21. app/ai/claude.py +20 -0
  22. app/ai/codex.py +34 -0
  23. app/ai/factory.py +27 -0
  24. app/ai/gemini.py +20 -0
  25. app/ai/model_catalog.py +47 -0
  26. app/ai/usage.py +134 -0
  27. app/cli.py +238 -0
  28. app/config.py +130 -0
  29. app/git/__init__.py +0 -0
  30. app/git/ai_commit.py +88 -0
  31. app/git/branch_naming.py +21 -0
  32. app/git/commit_message.py +279 -0
  33. app/git/service.py +669 -0
  34. app/jobs/__init__.py +0 -0
  35. app/jobs/manager.py +770 -0
  36. app/jobs/schemas.py +116 -0
  37. app/jobs/store.py +334 -0
  38. app/main.py +265 -0
  39. app/models.py +20 -0
  40. app/monitoring/__init__.py +10 -0
  41. app/monitoring/code.py +161 -0
  42. app/monitoring/events.py +33 -0
  43. app/monitoring/git.py +103 -0
  44. app/monitoring/log_buffer.py +245 -0
  45. app/monitoring/memory.py +19 -0
  46. app/monitoring/model.py +598 -0
  47. app/projects/__init__.py +19 -0
  48. app/projects/registry.py +384 -0
  49. app/security/__init__.py +0 -0
  50. app/security/auth.py +19 -0
  51. app/system_startup.py +34 -0
  52. app/telegram/__init__.py +0 -0
  53. app/telegram/bot_instances.py +67 -0
  54. app/telegram/commands/__init__.py +64 -0
  55. app/telegram/commands/base.py +222 -0
  56. app/telegram/commands/branch.py +366 -0
  57. app/telegram/commands/clear_stop.py +221 -0
  58. app/telegram/commands/fix.py +219 -0
  59. app/telegram/commands/model.py +93 -0
  60. app/telegram/commands/monitor.py +185 -0
  61. app/telegram/commands/registry.py +110 -0
  62. app/telegram/commands/status.py +243 -0
  63. app/telegram/commands/system.py +201 -0
  64. app/telegram/confirmations.py +36 -0
  65. app/telegram/conversation.py +789 -0
  66. app/telegram/i18n.py +742 -0
  67. app/telegram/model_preferences.py +53 -0
  68. app/telegram/notifier.py +387 -0
  69. app/telegram/parser.py +267 -0
  70. app/telegram/webhook.py +988 -0
  71. app/telegram/webhook_registration.py +172 -0
  72. app/tunnel.py +104 -0
  73. remote_coder-0.4.1.dist-info/METADATA +520 -0
  74. remote_coder-0.4.1.dist-info/RECORD +78 -0
  75. remote_coder-0.4.1.dist-info/WHEEL +5 -0
  76. remote_coder-0.4.1.dist-info/entry_points.txt +2 -0
  77. remote_coder-0.4.1.dist-info/licenses/LICENSE +201 -0
  78. remote_coder-0.4.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,635 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title data-i18n="title.advanced">Remote AI Coder - Advanced Settings</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0f1419;
10
+ --bg-elevated: #1a2332;
11
+ --surface: #243044;
12
+ --border: rgba(255, 255, 255, 0.08);
13
+ --text: #e8edf4;
14
+ --text-muted: #8b9cb3;
15
+ --accent: #3d9cf5;
16
+ --accent-dim: rgba(61, 156, 245, 0.15);
17
+ --success: #34c759;
18
+ --success-bg: rgba(52, 199, 89, 0.12);
19
+ --danger: #ff453a;
20
+ --danger-bg: rgba(255, 69, 58, 0.12);
21
+ --warning: #ffcc00;
22
+ --radius: 12px;
23
+ --radius-sm: 8px;
24
+ --shadow: 0 4px 24px rgba(0, 0, 0, 0.35);
25
+ --font: "Segoe UI", system-ui, -apple-system, sans-serif;
26
+ --focus: 0 0 0 2px var(--bg), 0 0 0 4px var(--accent);
27
+ }
28
+
29
+ *, *::before, *::after { box-sizing: border-box; }
30
+
31
+ body {
32
+ margin: 0;
33
+ min-height: 100vh;
34
+ font-family: var(--font);
35
+ font-size: 15px;
36
+ line-height: 1.5;
37
+ color: var(--text);
38
+ background: radial-gradient(ellipse 120% 80% at 50% -20%, #1e3a5f 0%, var(--bg) 55%);
39
+ padding: 1.25rem 1rem 2.5rem;
40
+ }
41
+
42
+ .wrap { max-width: 72rem; margin: 0 auto; }
43
+
44
+ .app-header {
45
+ margin-bottom: 1.5rem;
46
+ padding: 1.25rem 1.35rem;
47
+ background: linear-gradient(135deg, var(--bg-elevated) 0%, var(--surface) 100%);
48
+ border: 1px solid var(--border);
49
+ border-radius: var(--radius);
50
+ box-shadow: var(--shadow);
51
+ }
52
+
53
+ .app-header h1 {
54
+ margin: 0 0 0.35rem;
55
+ font-size: 1.5rem;
56
+ font-weight: 700;
57
+ letter-spacing: -0.02em;
58
+ }
59
+
60
+ .app-header .tagline {
61
+ margin: 0;
62
+ color: var(--text-muted);
63
+ font-size: 0.9rem;
64
+ }
65
+
66
+ .localhost-pill {
67
+ display: inline-flex;
68
+ align-items: center;
69
+ gap: 0.35rem;
70
+ margin-top: 0.75rem;
71
+ padding: 0.35rem 0.65rem;
72
+ font-size: 0.8rem;
73
+ font-weight: 600;
74
+ color: var(--warning);
75
+ background: rgba(255, 204, 0, 0.1);
76
+ border: 1px solid rgba(255, 204, 0, 0.25);
77
+ border-radius: 999px;
78
+ }
79
+
80
+ .header-row {
81
+ display: flex;
82
+ flex-wrap: wrap;
83
+ align-items: flex-start;
84
+ justify-content: space-between;
85
+ gap: 1rem;
86
+ }
87
+
88
+ .header-icon-nav {
89
+ display: flex;
90
+ flex-shrink: 0;
91
+ gap: 0.35rem;
92
+ align-items: center;
93
+ }
94
+
95
+ .header-icon-link {
96
+ display: flex;
97
+ align-items: center;
98
+ justify-content: center;
99
+ width: 2.5rem;
100
+ height: 2.5rem;
101
+ border-radius: var(--radius-sm);
102
+ border: 1px solid var(--border);
103
+ background: rgba(0, 0, 0, 0.15);
104
+ transition: background 0.15s, border-color 0.15s;
105
+ }
106
+
107
+ .header-icon-link:hover {
108
+ background: #2d3d54;
109
+ border-color: rgba(255, 255, 255, 0.12);
110
+ }
111
+
112
+ .header-icon-link:focus-visible {
113
+ outline: none;
114
+ box-shadow: var(--focus);
115
+ }
116
+
117
+ .header-icon-link.is-current {
118
+ border-color: rgba(61, 156, 245, 0.45);
119
+ background: var(--accent-dim);
120
+ }
121
+
122
+ .header-icon-link img {
123
+ display: block;
124
+ width: 1.35rem;
125
+ height: 1.35rem;
126
+ }
127
+
128
+ .summary-grid {
129
+ display: grid;
130
+ grid-template-columns: repeat(auto-fill, minmax(10.5rem, 1fr));
131
+ gap: 0.75rem;
132
+ margin-bottom: 1.25rem;
133
+ }
134
+
135
+ .stat-card {
136
+ padding: 1rem 1.1rem;
137
+ background: var(--bg-elevated);
138
+ border: 1px solid var(--border);
139
+ border-radius: var(--radius-sm);
140
+ }
141
+
142
+ .stat-card .label {
143
+ margin: 0;
144
+ font-size: 0.72rem;
145
+ font-weight: 600;
146
+ text-transform: uppercase;
147
+ letter-spacing: 0.06em;
148
+ color: var(--text-muted);
149
+ }
150
+
151
+ .stat-card .value {
152
+ margin: 0.25rem 0 0;
153
+ font-size: 1.35rem;
154
+ font-weight: 700;
155
+ color: var(--text);
156
+ }
157
+
158
+ .stat-card .sub {
159
+ margin: 0.2rem 0 0;
160
+ font-size: 0.78rem;
161
+ color: var(--text-muted);
162
+ word-break: break-all;
163
+ }
164
+
165
+ #global-msg {
166
+ margin-bottom: 1rem;
167
+ padding: 0.65rem 1rem;
168
+ border-radius: var(--radius-sm);
169
+ font-size: 0.9rem;
170
+ display: none;
171
+ }
172
+
173
+ #global-msg.is-visible { display: block; }
174
+
175
+ #global-msg.msg-err {
176
+ color: #ffb4b0;
177
+ background: var(--danger-bg);
178
+ border: 1px solid rgba(255, 69, 58, 0.35);
179
+ }
180
+
181
+ #global-msg.msg-ok {
182
+ color: #9ee6b0;
183
+ background: var(--success-bg);
184
+ border: 1px solid rgba(52, 199, 89, 0.35);
185
+ }
186
+
187
+ #global-msg.msg-info {
188
+ color: var(--text-muted);
189
+ background: var(--accent-dim);
190
+ border: 1px solid rgba(61, 156, 245, 0.25);
191
+ }
192
+
193
+ section.card {
194
+ margin-bottom: 1.25rem;
195
+ padding: 1.25rem 1.35rem;
196
+ background: var(--bg-elevated);
197
+ border: 1px solid var(--border);
198
+ border-radius: var(--radius);
199
+ box-shadow: var(--shadow);
200
+ }
201
+
202
+ section.card h2 {
203
+ margin: 0 0 0.5rem;
204
+ font-size: 1.05rem;
205
+ font-weight: 700;
206
+ }
207
+
208
+ section.card > .lead {
209
+ margin: 0 0 1rem;
210
+ color: var(--text-muted);
211
+ font-size: 0.88rem;
212
+ }
213
+
214
+ section.card > .lead code {
215
+ padding: 0.12rem 0.35rem;
216
+ font-size: 0.82em;
217
+ background: var(--surface);
218
+ border-radius: 4px;
219
+ border: 1px solid var(--border);
220
+ }
221
+
222
+ .lead.danger {
223
+ color: #ffb4b0;
224
+ border-left: 3px solid var(--danger);
225
+ padding-left: 0.75rem;
226
+ }
227
+
228
+ label.field {
229
+ display: block;
230
+ margin-top: 0.85rem;
231
+ font-weight: 600;
232
+ font-size: 0.78rem;
233
+ text-transform: uppercase;
234
+ letter-spacing: 0.04em;
235
+ color: var(--text-muted);
236
+ }
237
+
238
+ label.field:first-of-type { margin-top: 0; }
239
+
240
+ .hint {
241
+ margin: 0.2rem 0 0;
242
+ font-size: 0.78rem;
243
+ font-weight: 400;
244
+ color: var(--text-muted);
245
+ text-transform: none;
246
+ letter-spacing: normal;
247
+ }
248
+
249
+ input {
250
+ width: 100%;
251
+ max-width: 100%;
252
+ margin-top: 0.35rem;
253
+ padding: 0.55rem 0.65rem;
254
+ font-family: inherit;
255
+ font-size: 0.95rem;
256
+ color: var(--text);
257
+ background: var(--bg);
258
+ border: 1px solid var(--border);
259
+ border-radius: var(--radius-sm);
260
+ }
261
+
262
+ select {
263
+ width: 100%;
264
+ max-width: 18rem;
265
+ margin-top: 0.35rem;
266
+ padding: 0.55rem 0.65rem;
267
+ font-family: inherit;
268
+ font-size: 0.95rem;
269
+ color: var(--text);
270
+ background: var(--bg);
271
+ border: 1px solid var(--border);
272
+ border-radius: var(--radius-sm);
273
+ }
274
+
275
+ input:focus {
276
+ outline: none;
277
+ border-color: var(--accent);
278
+ box-shadow: 0 0 0 3px var(--accent-dim);
279
+ }
280
+
281
+ label.checkbox {
282
+ display: flex;
283
+ align-items: center;
284
+ gap: 0.5rem;
285
+ margin-top: 0.85rem;
286
+ font-weight: 600;
287
+ font-size: 0.9rem;
288
+ color: var(--text);
289
+ text-transform: none;
290
+ letter-spacing: normal;
291
+ }
292
+
293
+ label.checkbox input {
294
+ width: auto;
295
+ margin: 0;
296
+ }
297
+
298
+ .form-actions {
299
+ display: flex;
300
+ flex-wrap: wrap;
301
+ gap: 0.5rem;
302
+ margin-top: 1.25rem;
303
+ }
304
+
305
+ button {
306
+ font-family: inherit;
307
+ font-size: 0.82rem;
308
+ font-weight: 600;
309
+ padding: 0.45rem 0.75rem;
310
+ border-radius: var(--radius-sm);
311
+ border: 1px solid var(--border);
312
+ background: var(--surface);
313
+ color: var(--text);
314
+ cursor: pointer;
315
+ transition: background 0.15s, border-color 0.15s, opacity 0.15s;
316
+ }
317
+
318
+ button:hover:not(:disabled) {
319
+ background: #2d3d54;
320
+ border-color: rgba(255, 255, 255, 0.12);
321
+ }
322
+
323
+ button:focus-visible {
324
+ outline: none;
325
+ box-shadow: var(--focus);
326
+ }
327
+
328
+ button:disabled {
329
+ opacity: 0.45;
330
+ cursor: not-allowed;
331
+ }
332
+
333
+ button.btn-primary {
334
+ background: var(--accent);
335
+ border-color: transparent;
336
+ color: #0a1628;
337
+ }
338
+
339
+ button.btn-primary:hover:not(:disabled) {
340
+ background: #5aadf7;
341
+ }
342
+
343
+ button.btn-ghost {
344
+ background: transparent;
345
+ }
346
+
347
+ @media (max-width: 640px) {
348
+ .app-header h1 { font-size: 1.25rem; }
349
+ }
350
+ </style>
351
+ </head>
352
+ <body>
353
+ <div class="wrap">
354
+ <header class="app-header">
355
+ <div class="header-row">
356
+ <div>
357
+ <h1 data-i18n="advanced.h1">Advanced Settings</h1>
358
+ <p class="tagline" data-i18n="advanced.tagline">Options that can affect repositories and conversation memory.</p>
359
+ </div>
360
+ <nav class="header-icon-nav" aria-label="Page navigation" data-i18n-aria-label="nav.aria">
361
+ <a href="/" class="header-icon-link" title="Admin home" aria-label="Admin home" data-i18n-title="nav.home" data-i18n-aria-label="nav.home">
362
+ <img src="/admin-static/icons/home.svg" width="22" height="22" alt="" />
363
+ </a>
364
+ <a href="/projects" class="header-icon-link" title="Projects" aria-label="Projects" data-i18n-title="nav.projects" data-i18n-aria-label="nav.projects">
365
+ <img src="/admin-static/icons/projects.svg" width="22" height="22" alt="" />
366
+ </a>
367
+ <a href="/advanced" class="header-icon-link is-current" title="Advanced settings" aria-label="Advanced settings" data-i18n-title="nav.advanced" data-i18n-aria-label="nav.advanced">
368
+ <img src="/admin-static/icons/advanced.svg" width="22" height="22" alt="" />
369
+ </a>
370
+ <a href="/logs" class="header-icon-link" title="Server logs" aria-label="Server logs" data-i18n-title="nav.logs" data-i18n-aria-label="nav.logs">
371
+ <img src="/admin-static/icons/logs.svg" width="22" height="22" alt="" />
372
+ </a>
373
+ <a href="/database" class="header-icon-link" title="Data browser" aria-label="Data browser" data-i18n-title="nav.database" data-i18n-aria-label="nav.database">
374
+ <img src="/admin-static/icons/database.svg" width="22" height="22" alt="" />
375
+ </a>
376
+ </nav>
377
+ </div>
378
+ <span class="localhost-pill" aria-hidden="true" data-i18n="common.localOnly">Local only</span>
379
+ <p class="tagline" style="margin-top:0.65rem" data-i18n-html="common.localhostNote">This page is only available from <strong>127.0.0.1</strong>.</p>
380
+ </header>
381
+
382
+ <div id="global-msg" role="status" aria-live="polite"></div>
383
+
384
+ <div class="summary-grid" id="summary-grid" aria-label="Summary" data-i18n-aria-label="common.summaryAria"></div>
385
+
386
+ <form id="adv-form">
387
+
388
+ <!-- Telegram notifications -->
389
+ <section class="card" aria-labelledby="section-telegram">
390
+ <h2 id="section-telegram" data-i18n="advanced.secTelegram">Telegram Notifications</h2>
391
+ <p class="lead" data-i18n="advanced.secTelegramLead">Configure message display and server event notifications.</p>
392
+
393
+ <label class="field" for="adv-ui-language" data-i18n="advanced.uiLangLabel">Interface language</label>
394
+ <p class="hint" data-i18n="advanced.uiLangHint">Default: English. Choose Korean here for the Telegram bot and this admin UI.</p>
395
+ <select id="adv-ui-language" name="ui_language">
396
+ <option value="en">English</option>
397
+ <option value="ko">한국어</option>
398
+ </select>
399
+
400
+ <label class="field" for="adv-status-job-limit" data-i18n="advanced.statusLimitLabel">Recent jobs shown by /status</label>
401
+ <p class="hint" data-i18n="advanced.statusLimitHint">Maximum number of recent jobs selectable with inline buttons in /status. Default: 10</p>
402
+ <input type="number" id="adv-status-job-limit" name="status_recent_job_limit" min="1" max="50" step="1" placeholder="Example: 10" data-i18n-placeholder="advanced.phStatus" autocomplete="off" />
403
+
404
+ <label class="checkbox" style="margin-top:1.1rem">
405
+ <input type="checkbox" id="adv-natural-confirm-buttons" name="natural_job_confirmation_buttons_enabled" />
406
+ <span data-i18n-html="advanced.naturalConfirmLabel">Use inline buttons instead of <code>y</code>/<code>Y</code> for natural-language job confirmations</span>
407
+ </label>
408
+ <p class="hint" style="margin:0.35rem 0 0 1.75rem" data-i18n-html="advanced.naturalConfirmHint">
409
+ Default: off. When enabled, job confirmation messages show <strong>Yes</strong>/<strong>No</strong> buttons.
410
+ </p>
411
+
412
+ <label class="checkbox" style="margin-top:1.1rem">
413
+ <input type="checkbox" id="adv-lifecycle-notify" name="server_lifecycle_notify_enabled" />
414
+ <span data-i18n="advanced.lifecycleLabel">Send Telegram notifications when the server starts or stops</span>
415
+ </label>
416
+ <p class="hint" style="margin:0.35rem 0 0 1.75rem" data-i18n="advanced.lifecycleHint">
417
+ Default: on. When disabled, restarts do not send server start/stop messages.
418
+ </p>
419
+ </section>
420
+
421
+ <!-- Job execution -->
422
+ <section class="card" aria-labelledby="section-job">
423
+ <h2 id="section-job" data-i18n="advanced.secJob">Job Execution</h2>
424
+ <p class="lead" data-i18n="advanced.secJobLead">Configure AI job execution limits.</p>
425
+
426
+ <label class="field" for="adv-job-timeout" data-i18n="advanced.jobTimeoutLabel">AI job timeout (seconds, blank uses environment default)</label>
427
+ <p class="hint" data-i18n="advanced.jobTimeoutHint">If a Claude/Codex/Gemini runner does not finish within this time, the job fails.</p>
428
+ <input type="number" id="adv-job-timeout" name="job_timeout_seconds" min="1" step="1" placeholder="Example: 3600" data-i18n-placeholder="advanced.phTimeout" autocomplete="off" />
429
+ </section>
430
+
431
+ <!-- Git integration -->
432
+ <section class="card" aria-labelledby="section-git">
433
+ <h2 id="section-git" data-i18n="advanced.secGit">Git Integration</h2>
434
+ <p class="lead danger" data-i18n="advanced.secGitLead">
435
+ These options can broadly affect repositories. Use them only when you understand the impact.
436
+ </p>
437
+
438
+ <label class="checkbox">
439
+ <input type="checkbox" id="adv-startup-pull" name="pull_projects_on_server_startup_enabled" />
440
+ <span data-i18n-html="advanced.startupPullLabel">Run <code>git pull</code> for registered active project repositories on server startup/restart</span>
441
+ </label>
442
+ <p class="hint" style="margin:0.35rem 0 0 1.75rem" data-i18n="advanced.startupPullHint">
443
+ Default: off. Pulls from the remote based on each active project's checked-out branch. Network errors, conflicts, or local changes can fail; the server still starts.
444
+ </p>
445
+
446
+ <label class="checkbox" style="margin-top:1.1rem">
447
+ <input type="checkbox" id="adv-merge-main" name="auto_merge_to_main_enabled" />
448
+ <span data-i18n-html="advanced.mergeMainLabel">Apply job results immediately to <code>main</code>/<code>master</code>, then push</span>
449
+ </label>
450
+ <p class="hint" style="margin:0.35rem 0 0 1.75rem" data-i18n-html="advanced.mergeMainHint">
451
+ When disabled, jobs only commit and push to their work branch. When enabled, successful jobs run an integration similar to <code>/rebase</code> (rebase -> main fast-forward merge -> push).
452
+ </p>
453
+
454
+ <label class="checkbox" style="margin-top:1.1rem">
455
+ <input type="checkbox" id="adv-delete-rebased-branch" name="delete_rebased_branch_enabled" />
456
+ <span data-i18n-html="advanced.deleteRebasedLabel">Delete the rebased branch locally and remotely after <code>/rebase</code></span>
457
+ </label>
458
+ <p class="hint" style="margin:0.35rem 0 0 1.75rem" data-i18n-html="advanced.deleteRebasedHint">
459
+ Default: on. When disabled, <code>/rebase</code> only merges into main/master and pushes; the target branch remains.
460
+ </p>
461
+ </section>
462
+
463
+ <!-- Conversation memory -->
464
+ <section class="card" aria-labelledby="section-memory">
465
+ <h2 id="section-memory" data-i18n="advanced.secMemory">Conversation Memory (SQLite)</h2>
466
+ <p class="lead danger" data-i18n="advanced.secMemoryLead">
467
+ These options can broadly affect the conversation memory SQLite database. Enable only when needed.
468
+ </p>
469
+
470
+ <label class="checkbox">
471
+ <input type="checkbox" id="adv-memory-enabled" name="conversation_memory_limit_enabled" />
472
+ <span data-i18n="advanced.memoryEnabledLabel">Limit SQLite conversation memory storage</span>
473
+ </label>
474
+ <p class="hint" style="margin:0.35rem 0 0 1.75rem" data-i18n-html="advanced.memoryEnabledHint">
475
+ When enabled, old <code>conversation_entries</code> rows are deleted <strong>globally</strong>. Set at least one positive row-count or DB-size limit.
476
+ </p>
477
+
478
+ <label class="field" for="adv-max-rows" data-i18n="advanced.maxRowsLabel">Maximum rows (blank disables)</label>
479
+ <input type="number" id="adv-max-rows" name="conversation_memory_max_rows" min="1" step="1" placeholder="Example: 5000" data-i18n-placeholder="advanced.phRows" autocomplete="off" />
480
+
481
+ <label class="field" for="adv-max-bytes" data-i18n="advanced.maxBytesLabel">Maximum DB size (bytes, blank disables)</label>
482
+ <input type="number" id="adv-max-bytes" name="conversation_memory_max_bytes" min="1" step="1" placeholder="Example: 10485760" data-i18n-placeholder="advanced.phBytes" autocomplete="off" />
483
+ </section>
484
+
485
+ <div class="form-actions">
486
+ <button type="submit" class="btn-primary" id="btn-adv-save" data-i18n="advanced.btnSave">Save</button>
487
+ <button type="button" class="btn-ghost" id="btn-adv-reload" data-i18n="advanced.btnReload">Reload</button>
488
+ </div>
489
+
490
+ </form>
491
+ </div>
492
+
493
+ <script src="/admin-static/i18n.js"></script>
494
+ <script src="/admin-static/summary.js"></script>
495
+ <script>
496
+ const $ = (s) => document.querySelector(s);
497
+ const globalMsg = $("#global-msg");
498
+ let busy = false;
499
+
500
+ async function loadSummary() {
501
+ const result = await adminSummary.loadSummaryGrid((msg) => setGlobalMsg("err", msg));
502
+ return result.ok;
503
+ }
504
+
505
+ function setGlobalMsg(kind, text) {
506
+ if (!text) {
507
+ globalMsg.textContent = "";
508
+ globalMsg.className = "";
509
+ return;
510
+ }
511
+ globalMsg.textContent = text;
512
+ globalMsg.className =
513
+ "is-visible " + (kind === "err" ? "msg-err" : kind === "ok" ? "msg-ok" : "msg-info");
514
+ }
515
+
516
+ async function parseApiError(r, rawText) {
517
+ const ct = (r.headers.get("content-type") || "").toLowerCase();
518
+ if (ct.includes("application/json")) {
519
+ try {
520
+ const j = JSON.parse(rawText);
521
+ if (j && typeof j.detail === "string") return j.detail;
522
+ if (Array.isArray(j.detail)) {
523
+ return j.detail.map((d) => (typeof d.msg === "string" ? d.msg : JSON.stringify(d))).join("\n");
524
+ }
525
+ } catch (_) { /* ignore */ }
526
+ }
527
+ return rawText || "Request failed (" + r.status + ")";
528
+ }
529
+
530
+ function parseOptionalPositiveInt(raw) {
531
+ const s = String(raw ?? "").trim();
532
+ if (!s) return null;
533
+ const n = Number(s);
534
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) return null;
535
+ return n;
536
+ }
537
+
538
+ function syncAdvMemoryInputs() {
539
+ const on = $("#adv-memory-enabled").checked;
540
+ $("#adv-max-rows").disabled = !on;
541
+ $("#adv-max-bytes").disabled = !on;
542
+ }
543
+
544
+ function setBusy(v) {
545
+ busy = v;
546
+ document.querySelectorAll("#adv-form button").forEach((b) => {
547
+ b.disabled = v;
548
+ });
549
+ }
550
+
551
+ async function loadAdvancedSettings() {
552
+ const r = await fetch("/api/advanced-settings");
553
+ const t = await r.text();
554
+ if (!r.ok) {
555
+ setGlobalMsg("err", await parseApiError(r, t));
556
+ return false;
557
+ }
558
+ const d = JSON.parse(t);
559
+ $("#adv-ui-language").value = d.ui_language || "en";
560
+ $("#adv-status-job-limit").value =
561
+ d.status_recent_job_limit != null ? String(d.status_recent_job_limit) : "10";
562
+ $("#adv-job-timeout").value =
563
+ d.job_timeout_seconds != null ? String(d.job_timeout_seconds) : "";
564
+ $("#adv-lifecycle-notify").checked = d.server_lifecycle_notify_enabled !== false;
565
+ $("#adv-startup-pull").checked = !!d.pull_projects_on_server_startup_enabled;
566
+ $("#adv-merge-main").checked = !!d.auto_merge_to_main_enabled;
567
+ $("#adv-delete-rebased-branch").checked = d.delete_rebased_branch_enabled !== false;
568
+ $("#adv-natural-confirm-buttons").checked = !!d.natural_job_confirmation_buttons_enabled;
569
+ $("#adv-memory-enabled").checked = !!d.conversation_memory_limit_enabled;
570
+ $("#adv-max-rows").value =
571
+ d.conversation_memory_max_rows != null ? String(d.conversation_memory_max_rows) : "";
572
+ $("#adv-max-bytes").value =
573
+ d.conversation_memory_max_bytes != null ? String(d.conversation_memory_max_bytes) : "";
574
+ syncAdvMemoryInputs();
575
+ return true;
576
+ }
577
+
578
+ $("#adv-memory-enabled").addEventListener("change", syncAdvMemoryInputs);
579
+
580
+ $("#btn-adv-reload").addEventListener("click", async () => {
581
+ if (busy) return;
582
+ setBusy(true);
583
+ setGlobalMsg("", "");
584
+ try {
585
+ const [okSum, okAdv] = await Promise.all([loadSummary(), loadAdvancedSettings()]);
586
+ if (okSum && okAdv) {
587
+ setGlobalMsg("ok", i18n.t("advanced.loadedMsg"));
588
+ }
589
+ } finally {
590
+ setBusy(false);
591
+ }
592
+ });
593
+
594
+ $("#adv-form").addEventListener("submit", async (e) => {
595
+ e.preventDefault();
596
+ if (busy) return;
597
+ const jobLimitRaw = parseInt($("#adv-status-job-limit").value, 10);
598
+ const body = {
599
+ status_recent_job_limit: Number.isFinite(jobLimitRaw) && jobLimitRaw >= 1 ? jobLimitRaw : 10,
600
+ ui_language: $("#adv-ui-language").value === "ko" ? "ko" : "en",
601
+ job_timeout_seconds: parseOptionalPositiveInt($("#adv-job-timeout").value),
602
+ server_lifecycle_notify_enabled: $("#adv-lifecycle-notify").checked,
603
+ pull_projects_on_server_startup_enabled: $("#adv-startup-pull").checked,
604
+ auto_merge_to_main_enabled: $("#adv-merge-main").checked,
605
+ delete_rebased_branch_enabled: $("#adv-delete-rebased-branch").checked,
606
+ natural_job_confirmation_buttons_enabled: $("#adv-natural-confirm-buttons").checked,
607
+ conversation_memory_limit_enabled: $("#adv-memory-enabled").checked,
608
+ conversation_memory_max_rows: parseOptionalPositiveInt($("#adv-max-rows").value),
609
+ conversation_memory_max_bytes: parseOptionalPositiveInt($("#adv-max-bytes").value),
610
+ };
611
+ setGlobalMsg("", "");
612
+ setBusy(true);
613
+ try {
614
+ const r = await fetch("/api/advanced-settings", {
615
+ method: "PUT",
616
+ headers: { "Content-Type": "application/json" },
617
+ body: JSON.stringify(body),
618
+ });
619
+ const t = await r.text();
620
+ if (!r.ok) {
621
+ setGlobalMsg("err", await parseApiError(r, t));
622
+ return;
623
+ }
624
+ setGlobalMsg("ok", i18n.t("advanced.savedMsg"));
625
+ } finally {
626
+ setBusy(false);
627
+ }
628
+ });
629
+
630
+ (async function loadPage() {
631
+ await Promise.all([loadSummary(), loadAdvancedSettings()]);
632
+ })();
633
+ </script>
634
+ </body>
635
+ </html>