@zhongqian97-code/ecode 0.5.20 → 0.5.22
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/dist/index.js +243 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5250,7 +5250,7 @@ function generateAdminHtml(version2) {
|
|
|
5250
5250
|
<html lang="zh">
|
|
5251
5251
|
<head>
|
|
5252
5252
|
<meta charset="UTF-8">
|
|
5253
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
5253
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, interactive-widget=resizes-content">
|
|
5254
5254
|
<title>ecode web admin</title>
|
|
5255
5255
|
<style>
|
|
5256
5256
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
@@ -5552,6 +5552,28 @@ function generateAdminHtml(version2) {
|
|
|
5552
5552
|
}
|
|
5553
5553
|
#approve-btn:hover { background: #2ea043; color: #fff; }
|
|
5554
5554
|
|
|
5555
|
+
/* \u2500\u2500 Config & upgrade modals \u2500\u2500 */
|
|
5556
|
+
.modal-overlay { display:none; position:fixed; inset:0; background:rgba(0,0,0,.7); z-index:100; align-items:center; justify-content:center; }
|
|
5557
|
+
.modal-overlay.open { display:flex; }
|
|
5558
|
+
.modal { background:#161b22; border:1px solid #30363d; border-radius:8px; padding:24px; width:min(480px,90vw); max-height:80vh; overflow-y:auto; }
|
|
5559
|
+
.modal h3 { color:#e6edf3; margin-bottom:16px; font-size:14px; }
|
|
5560
|
+
.form-row { margin-bottom:12px; }
|
|
5561
|
+
.form-row label { display:block; font-size:12px; color:#8b949e; margin-bottom:4px; }
|
|
5562
|
+
.form-row input, .form-row textarea { width:100%; background:#0d1117; border:1px solid #30363d; border-radius:4px; color:#c9d1d9; font-family:monospace; font-size:13px; padding:6px 8px; }
|
|
5563
|
+
.form-row textarea { resize:vertical; min-height:60px; }
|
|
5564
|
+
.btn { padding:6px 14px; border-radius:4px; border:1px solid #30363d; cursor:pointer; font-family:monospace; font-size:13px; }
|
|
5565
|
+
.btn-primary { background:#1f6feb; color:#fff; border-color:#1f6feb; }
|
|
5566
|
+
.btn-danger { background:#da3633; color:#fff; border-color:#da3633; }
|
|
5567
|
+
.modal-footer { display:flex; gap:8px; justify-content:flex-end; margin-top:16px; }
|
|
5568
|
+
#upgrade-output { display:none; background:#0d1117; border:1px solid #30363d; border-radius:4px; padding:8px; font-family:monospace; font-size:12px; color:#c9d1d9; white-space:pre-wrap; max-height:200px; overflow-y:auto; margin-top:8px; }
|
|
5569
|
+
|
|
5570
|
+
/* \u2500\u2500 Syntax highlight \u2500\u2500 */
|
|
5571
|
+
.kw { color: #ff7b72; }
|
|
5572
|
+
.str { color: #a5d6ff; }
|
|
5573
|
+
.cmt { color: #8b949e; font-style: italic; }
|
|
5574
|
+
.num { color: #79c0ff; }
|
|
5575
|
+
.fn { color: #d2a8ff; }
|
|
5576
|
+
|
|
5555
5577
|
/* \u2500\u2500 Mobile \u2500\u2500 */
|
|
5556
5578
|
@media (max-width: 600px) {
|
|
5557
5579
|
#hamburger { display: block; }
|
|
@@ -5571,6 +5593,8 @@ function generateAdminHtml(version2) {
|
|
|
5571
5593
|
<button id="hamburger" aria-label="Toggle sidebar">\u2630</button>
|
|
5572
5594
|
<h1>\u26A1 ecode web admin</h1>
|
|
5573
5595
|
<span class="version">v${version2}</span>
|
|
5596
|
+
<button id="config-btn" class="btn">\u914D\u7F6E</button>
|
|
5597
|
+
<button id="upgrade-btn" class="btn">\u5347\u7EA7</button>
|
|
5574
5598
|
</div>
|
|
5575
5599
|
|
|
5576
5600
|
<div id="app">
|
|
@@ -5604,6 +5628,35 @@ function generateAdminHtml(version2) {
|
|
|
5604
5628
|
</div>
|
|
5605
5629
|
</div>
|
|
5606
5630
|
|
|
5631
|
+
<!-- Config modal -->
|
|
5632
|
+
<div id="config-modal" class="modal-overlay">
|
|
5633
|
+
<div class="modal">
|
|
5634
|
+
<h3>\u2699 \u914D\u7F6E</h3>
|
|
5635
|
+
<div class="form-row"><label>Model</label><input id="cfg-model" name="model" type="text" /></div>
|
|
5636
|
+
<div class="form-row"><label>Base URL</label><input id="cfg-baseurl" name="baseUrl" type="text" /></div>
|
|
5637
|
+
<div class="form-row"><label>API Key</label><input id="cfg-apikey" name="apiKey" type="password" placeholder="\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" /></div>
|
|
5638
|
+
<div class="form-row"><label>Log Dir</label><input id="cfg-logdir" name="logDir" type="text" /></div>
|
|
5639
|
+
<div class="form-row"><label>System Prompt</label><textarea id="cfg-systemprompt"></textarea></div>
|
|
5640
|
+
<div class="modal-footer">
|
|
5641
|
+
<button id="cancel-config" class="btn">\u53D6\u6D88</button>
|
|
5642
|
+
<button id="save-config" class="btn btn-primary">\u4FDD\u5B58</button>
|
|
5643
|
+
</div>
|
|
5644
|
+
</div>
|
|
5645
|
+
</div>
|
|
5646
|
+
|
|
5647
|
+
<!-- Upgrade modal -->
|
|
5648
|
+
<div id="upgrade-modal" class="modal-overlay">
|
|
5649
|
+
<div class="modal">
|
|
5650
|
+
<h3>\u2B06 \u5347\u7EA7 ecode</h3>
|
|
5651
|
+
<div id="upgrade-status">\u6B63\u5728\u68C0\u67E5\u7248\u672C\u2026</div>
|
|
5652
|
+
<div id="upgrade-output"></div>
|
|
5653
|
+
<div class="modal-footer">
|
|
5654
|
+
<button id="cancel-upgrade" class="btn">\u5173\u95ED</button>
|
|
5655
|
+
<button id="confirm-upgrade" class="btn btn-primary" disabled>\u5347\u7EA7</button>
|
|
5656
|
+
</div>
|
|
5657
|
+
</div>
|
|
5658
|
+
</div>
|
|
5659
|
+
|
|
5607
5660
|
<!-- Bash approval modal -->
|
|
5608
5661
|
<div id="approval-modal" role="dialog" aria-modal="true">
|
|
5609
5662
|
<div id="approval-box">
|
|
@@ -5636,7 +5689,7 @@ function generateAdminHtml(version2) {
|
|
|
5636
5689
|
return path + sep + 'token=' + encodeURIComponent(state.token);
|
|
5637
5690
|
}
|
|
5638
5691
|
|
|
5639
|
-
async function apiFetch(path, opts) {
|
|
5692
|
+
async function apiFetch(path, opts = {}) {
|
|
5640
5693
|
const url = apiUrl(path);
|
|
5641
5694
|
const res = await fetch(url, opts);
|
|
5642
5695
|
if (!res.ok) throw new Error(res.status + ' ' + res.statusText);
|
|
@@ -5648,6 +5701,21 @@ function generateAdminHtml(version2) {
|
|
|
5648
5701
|
return proto + '//' + location.host + '/api/ws/sessions/' + sessionId + '?token=' + encodeURIComponent(state.token);
|
|
5649
5702
|
}
|
|
5650
5703
|
|
|
5704
|
+
// \u2500\u2500 Code highlighting \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5705
|
+
function highlightCode(code) {
|
|
5706
|
+
let s = escHtml(code);
|
|
5707
|
+
// keywords
|
|
5708
|
+
s = s.replace(/\b(function|const|let|var|return|if|else|for|while|class|import|export|from|async|await|new|this|typeof|instanceof|null|undefined|true|false)\b/g, '<span class="kw">$1</span>');
|
|
5709
|
+
// strings (single, double \u2014 after HTML escaping, quotes appear as ' or ")
|
|
5710
|
+
s = s.replace(/('[^&#]*'|"[^&]*")/g, '<span class="str">$1</span>');
|
|
5711
|
+
// comments
|
|
5712
|
+
s = s.replace(/(//[^
|
|
5713
|
+
]*)/g, '<span class="cmt">$1</span>');
|
|
5714
|
+
// numbers
|
|
5715
|
+
s = s.replace(/\b(d+.?d*)\b/g, '<span class="num">$1</span>');
|
|
5716
|
+
return s;
|
|
5717
|
+
}
|
|
5718
|
+
|
|
5651
5719
|
// Basic markdown \u2192 HTML (no libs)
|
|
5652
5720
|
function renderMarkdown(text) {
|
|
5653
5721
|
// Escape HTML first
|
|
@@ -5655,9 +5723,9 @@ function generateAdminHtml(version2) {
|
|
|
5655
5723
|
.replace(/&/g, '&')
|
|
5656
5724
|
.replace(/</g, '<')
|
|
5657
5725
|
.replace(/>/g, '>');
|
|
5658
|
-
// Code blocks
|
|
5726
|
+
// Code blocks (use highlightCode for syntax coloring)
|
|
5659
5727
|
s = s.replace(/\`\`\`([\\s\\S]*?)\`\`\`/g, (_, code) =>
|
|
5660
|
-
'<pre><code>' + code.trim() + '</code></pre>');
|
|
5728
|
+
'<pre><code>' + highlightCode(code.trim()) + '</code></pre>');
|
|
5661
5729
|
// Inline code
|
|
5662
5730
|
s = s.replace(/\`([^\`]+)\`/g, '<code>$1</code>');
|
|
5663
5731
|
// Bold
|
|
@@ -5735,6 +5803,31 @@ function generateAdminHtml(version2) {
|
|
|
5735
5803
|
state.streamingMsgEl = null;
|
|
5736
5804
|
}
|
|
5737
5805
|
|
|
5806
|
+
async function loadMessages(sessionId) {
|
|
5807
|
+
try {
|
|
5808
|
+
const sep = '/api/sessions/' + sessionId + '/messages?token=' + encodeURIComponent(state.token);
|
|
5809
|
+
const res = await fetch(sep);
|
|
5810
|
+
if (!res.ok) return;
|
|
5811
|
+
const text = await res.text();
|
|
5812
|
+
const lines = text.split('\\n').filter(l => l.trim());
|
|
5813
|
+
const msgsEl = document.getElementById('messages');
|
|
5814
|
+
msgsEl.innerHTML = '';
|
|
5815
|
+
for (const line of lines) {
|
|
5816
|
+
try {
|
|
5817
|
+
const m = JSON.parse(line);
|
|
5818
|
+
if (m.content) {
|
|
5819
|
+
appendMessage(m.role === 'user' ? 'user' : 'assistant', renderMarkdown(m.content));
|
|
5820
|
+
}
|
|
5821
|
+
} catch { /* skip malformed lines */ }
|
|
5822
|
+
}
|
|
5823
|
+
if (!lines.length) {
|
|
5824
|
+
msgsEl.innerHTML = '<div class="empty-chat">\u6682\u65E0\u5386\u53F2\u6D88\u606F\uFF0C\u5F00\u59CB\u5BF9\u8BDD\u5427</div>';
|
|
5825
|
+
}
|
|
5826
|
+
} catch (e) {
|
|
5827
|
+
// silently ignore \u2014 session might have no log file yet
|
|
5828
|
+
}
|
|
5829
|
+
}
|
|
5830
|
+
|
|
5738
5831
|
function appendMessage(role, htmlContent, opts) {
|
|
5739
5832
|
const msgsEl = document.getElementById('messages');
|
|
5740
5833
|
// Remove empty-chat placeholder
|
|
@@ -5802,7 +5895,12 @@ function generateAdminHtml(version2) {
|
|
|
5802
5895
|
finalizeStreamingMsg();
|
|
5803
5896
|
if (state.activeSessionId === sessionId && state.wsRetries < 5) {
|
|
5804
5897
|
state.wsRetries++;
|
|
5805
|
-
|
|
5898
|
+
const wsRetryDelay = Math.min(1000 * Math.pow(2, state.wsRetries), 30000);
|
|
5899
|
+
document.getElementById('ws-status').textContent =
|
|
5900
|
+
'\u91CD\u8FDE\u4E2D\u2026 (' + state.wsRetries + '/5\uFF0C' + (wsRetryDelay/1000).toFixed(0) + 's\u540E)';
|
|
5901
|
+
setTimeout(() => openWs(sessionId), wsRetryDelay);
|
|
5902
|
+
} else if (state.wsRetries >= 5) {
|
|
5903
|
+
document.getElementById('ws-status').textContent = '\u8FDE\u63A5\u5931\u8D25\uFF0C\u8BF7\u5237\u65B0\u9875\u9762';
|
|
5806
5904
|
}
|
|
5807
5905
|
};
|
|
5808
5906
|
}
|
|
@@ -5911,6 +6009,7 @@ function generateAdminHtml(version2) {
|
|
|
5911
6009
|
document.getElementById('chat-title').textContent =
|
|
5912
6010
|
session ? (session.title || id.slice(0, 12) + '\u2026') : id;
|
|
5913
6011
|
clearMessages();
|
|
6012
|
+
loadMessages(id);
|
|
5914
6013
|
renderSidebar();
|
|
5915
6014
|
setStatus('idle');
|
|
5916
6015
|
connectWs(id);
|
|
@@ -5979,6 +6078,108 @@ function generateAdminHtml(version2) {
|
|
|
5979
6078
|
document.getElementById('sidebar').classList.toggle('open');
|
|
5980
6079
|
});
|
|
5981
6080
|
|
|
6081
|
+
// \u2500\u2500 \u914D\u7F6E\u6A21\u6001\u6846 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6082
|
+
document.getElementById('config-btn').addEventListener('click', async () => {
|
|
6083
|
+
try {
|
|
6084
|
+
const data = await apiFetch('/api/config');
|
|
6085
|
+
document.getElementById('cfg-model').value = data.model || '';
|
|
6086
|
+
document.getElementById('cfg-baseurl').value = data.baseUrl || '';
|
|
6087
|
+
document.getElementById('cfg-logdir').value = data.logDir || '';
|
|
6088
|
+
document.getElementById('cfg-systemprompt').value = data.systemPrompt || '';
|
|
6089
|
+
} catch (e) {
|
|
6090
|
+
// open modal even if fetch fails; fields will be empty
|
|
6091
|
+
}
|
|
6092
|
+
document.getElementById('config-modal').classList.add('open');
|
|
6093
|
+
});
|
|
6094
|
+
|
|
6095
|
+
document.getElementById('cancel-config').addEventListener('click', () => {
|
|
6096
|
+
document.getElementById('config-modal').classList.remove('open');
|
|
6097
|
+
});
|
|
6098
|
+
|
|
6099
|
+
document.getElementById('save-config').addEventListener('click', async () => {
|
|
6100
|
+
const body = {
|
|
6101
|
+
model: document.getElementById('cfg-model').value,
|
|
6102
|
+
baseUrl: document.getElementById('cfg-baseurl').value,
|
|
6103
|
+
logDir: document.getElementById('cfg-logdir').value,
|
|
6104
|
+
systemPrompt: document.getElementById('cfg-systemprompt').value,
|
|
6105
|
+
};
|
|
6106
|
+
const apiKeyVal = document.getElementById('cfg-apikey').value;
|
|
6107
|
+
if (apiKeyVal) body.apiKey = apiKeyVal;
|
|
6108
|
+
try {
|
|
6109
|
+
await apiFetch('/api/config', { method: 'PUT', headers: {'Content-Type':'application/json'}, body: JSON.stringify(body) });
|
|
6110
|
+
} catch (e) {
|
|
6111
|
+
// ignore errors silently for now
|
|
6112
|
+
}
|
|
6113
|
+
document.getElementById('config-modal').classList.remove('open');
|
|
6114
|
+
});
|
|
6115
|
+
|
|
6116
|
+
// \u2500\u2500 \u5347\u7EA7 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6117
|
+
document.getElementById('upgrade-btn').addEventListener('click', async () => {
|
|
6118
|
+
const modal = document.getElementById('upgrade-modal');
|
|
6119
|
+
const statusEl = document.getElementById('upgrade-status');
|
|
6120
|
+
const outputEl = document.getElementById('upgrade-output');
|
|
6121
|
+
const confirmBtn = document.getElementById('confirm-upgrade');
|
|
6122
|
+
outputEl.style.display = 'none';
|
|
6123
|
+
outputEl.textContent = '';
|
|
6124
|
+
confirmBtn.disabled = true;
|
|
6125
|
+
statusEl.textContent = '\u6B63\u5728\u68C0\u67E5\u7248\u672C\u2026';
|
|
6126
|
+
modal.classList.add('open');
|
|
6127
|
+
try {
|
|
6128
|
+
const r = await fetch(apiUrl('/api/version/check'));
|
|
6129
|
+
const v = await r.json();
|
|
6130
|
+
if (v.needsUpdate) {
|
|
6131
|
+
statusEl.textContent = '\u5F53\u524D v' + v.current + '\uFF0C\u6700\u65B0 v' + v.latest + '\uFF0C\u786E\u8BA4\u5347\u7EA7\uFF1F';
|
|
6132
|
+
confirmBtn.disabled = false;
|
|
6133
|
+
} else {
|
|
6134
|
+
statusEl.textContent = '\u5DF2\u662F\u6700\u65B0\u7248\u672C v' + v.current;
|
|
6135
|
+
}
|
|
6136
|
+
} catch(e) {
|
|
6137
|
+
statusEl.textContent = '\u7248\u672C\u68C0\u67E5\u5931\u8D25\uFF1A' + e.message;
|
|
6138
|
+
}
|
|
6139
|
+
});
|
|
6140
|
+
|
|
6141
|
+
document.getElementById('cancel-upgrade').addEventListener('click', () => {
|
|
6142
|
+
document.getElementById('upgrade-modal').classList.remove('open');
|
|
6143
|
+
});
|
|
6144
|
+
|
|
6145
|
+
document.getElementById('confirm-upgrade').addEventListener('click', async () => {
|
|
6146
|
+
const outputEl = document.getElementById('upgrade-output');
|
|
6147
|
+
const confirmBtn = document.getElementById('confirm-upgrade');
|
|
6148
|
+
const statusEl = document.getElementById('upgrade-status');
|
|
6149
|
+
confirmBtn.disabled = true;
|
|
6150
|
+
outputEl.style.display = 'block';
|
|
6151
|
+
outputEl.textContent = '';
|
|
6152
|
+
statusEl.textContent = '\u5347\u7EA7\u4E2D\u2026';
|
|
6153
|
+
try {
|
|
6154
|
+
const r = await fetch(apiUrl('/api/system/upgrade'), { method: 'POST' });
|
|
6155
|
+
const reader = r.body.getReader();
|
|
6156
|
+
const decoder = new TextDecoder();
|
|
6157
|
+
while (true) {
|
|
6158
|
+
const { done, value } = await reader.read();
|
|
6159
|
+
if (done) break;
|
|
6160
|
+
outputEl.textContent += decoder.decode(value);
|
|
6161
|
+
outputEl.scrollTop = outputEl.scrollHeight;
|
|
6162
|
+
}
|
|
6163
|
+
statusEl.textContent = '\u5347\u7EA7\u5B8C\u6210\uFF0C\u8BF7\u91CD\u542F ecode web';
|
|
6164
|
+
} catch(e) {
|
|
6165
|
+
statusEl.textContent = '\u5347\u7EA7\u5931\u8D25\uFF1A' + e.message;
|
|
6166
|
+
}
|
|
6167
|
+
});
|
|
6168
|
+
|
|
6169
|
+
// \u2500\u2500 Mobile keyboard adaptation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6170
|
+
document.getElementById('msg-input').addEventListener('focus', () => {
|
|
6171
|
+
setTimeout(() => {
|
|
6172
|
+
document.getElementById('msg-input').scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
6173
|
+
}, 300);
|
|
6174
|
+
});
|
|
6175
|
+
|
|
6176
|
+
// \u2500\u2500 Global error boundary \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6177
|
+
window.onerror = function(msg, src, line) {
|
|
6178
|
+
const wsStatusEl = document.getElementById('ws-status');
|
|
6179
|
+
if (wsStatusEl) wsStatusEl.textContent = '\u9519\u8BEF: ' + msg;
|
|
6180
|
+
return false;
|
|
6181
|
+
};
|
|
6182
|
+
|
|
5982
6183
|
// \u2500\u2500 Init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5983
6184
|
loadSessions();
|
|
5984
6185
|
</script>
|
|
@@ -6279,6 +6480,42 @@ async function sessionHubRoutes(app, opts) {
|
|
|
6279
6480
|
);
|
|
6280
6481
|
}
|
|
6281
6482
|
|
|
6483
|
+
// src/web/routes/system.ts
|
|
6484
|
+
import { execSync } from "child_process";
|
|
6485
|
+
import { spawn } from "child_process";
|
|
6486
|
+
async function systemRoutes(app, opts) {
|
|
6487
|
+
app.get("/api/version/check", async (_request, _reply) => {
|
|
6488
|
+
const current = opts.version;
|
|
6489
|
+
let latest;
|
|
6490
|
+
try {
|
|
6491
|
+
latest = execSync("npm view @zhongqian97-code/ecode version", {
|
|
6492
|
+
encoding: "utf-8",
|
|
6493
|
+
timeout: 2e3
|
|
6494
|
+
}).trim();
|
|
6495
|
+
} catch {
|
|
6496
|
+
latest = "unknown";
|
|
6497
|
+
}
|
|
6498
|
+
const needsUpdate = latest !== "unknown" && latest !== current;
|
|
6499
|
+
return { current, latest, needsUpdate };
|
|
6500
|
+
});
|
|
6501
|
+
app.post("/api/system/upgrade", (_request, reply) => {
|
|
6502
|
+
reply.raw.setHeader("Content-Type", "text/plain");
|
|
6503
|
+
reply.raw.write("Upgrading @zhongqian97-code/ecode...\n");
|
|
6504
|
+
const child = spawn("npm", ["update", "-g", "@zhongqian97-code/ecode"]);
|
|
6505
|
+
child.stdout.on("data", (chunk) => {
|
|
6506
|
+
reply.raw.write(chunk);
|
|
6507
|
+
});
|
|
6508
|
+
child.stderr.on("data", (chunk) => {
|
|
6509
|
+
reply.raw.write(chunk);
|
|
6510
|
+
});
|
|
6511
|
+
child.on("close", () => {
|
|
6512
|
+
reply.raw.write("Upgrade complete. Please restart ecode web.\n");
|
|
6513
|
+
reply.raw.end();
|
|
6514
|
+
});
|
|
6515
|
+
return reply;
|
|
6516
|
+
});
|
|
6517
|
+
}
|
|
6518
|
+
|
|
6282
6519
|
// src/web/server.ts
|
|
6283
6520
|
async function buildServer(opts) {
|
|
6284
6521
|
const app = Fastify({ logger: false });
|
|
@@ -6306,6 +6543,7 @@ async function buildServer(opts) {
|
|
|
6306
6543
|
await app.register(configRoutes, { config: opts.config });
|
|
6307
6544
|
await app.register(automationRoutes, { config: opts.config });
|
|
6308
6545
|
await app.register(chatRoutes, { config: opts.config, manager: opts.manager });
|
|
6546
|
+
await app.register(systemRoutes, { version: opts.version });
|
|
6309
6547
|
await app.register(sessionHubRoutes, { manager: opts.manager });
|
|
6310
6548
|
return app;
|
|
6311
6549
|
}
|