mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-03 15:53:56 +00:00
gui: 多主题外观(6色板/背景图缓存)/连接认证(总网关登录窗自动抓Cookie)/中英切换/插件禁用
This commit is contained in:
@ -6,6 +6,7 @@ let state = {
|
||||
meta: {},
|
||||
pluginMeta: {},
|
||||
settingsPlugins: ['core'],
|
||||
disabledPlugins: [],
|
||||
currentView: 'chat',
|
||||
selectedSection: 'core',
|
||||
messages: [],
|
||||
@ -96,11 +97,7 @@ function L() { return state.lang }
|
||||
function toggleLang() {
|
||||
state.lang = state.lang === 'zh' ? 'en' : 'zh';
|
||||
localStorage.setItem('ha-lang', state.lang);
|
||||
document.querySelectorAll('[data-i18n]').forEach(function(el) {
|
||||
var k = el.getAttribute('data-i18n');
|
||||
var m = window._i18n && window._i18n[k];
|
||||
if (m) el.textContent = __(m[0], m[1]);
|
||||
});
|
||||
applyI18n();
|
||||
renderAll();
|
||||
}
|
||||
|
||||
@ -133,9 +130,130 @@ function toggleTheme() {
|
||||
setTheme(cur === 'light' ? 'dark' : 'light');
|
||||
}
|
||||
|
||||
// ===== Appearance: 主题色 / 背景图 =====
|
||||
var PALETTES_GUI = {
|
||||
sakura: '#ff7fac',
|
||||
cyan: '#2dd4bf',
|
||||
violet: '#a78bfa',
|
||||
emerald: '#34d399',
|
||||
amber: '#fbbf24',
|
||||
blue: '#60a5fa'
|
||||
};
|
||||
|
||||
function setColor(name) {
|
||||
document.documentElement.setAttribute('data-color', name);
|
||||
localStorage.setItem('ha-color', name);
|
||||
var pop = document.getElementById('palette-pop');
|
||||
if (!pop) return;
|
||||
var btns = pop.querySelectorAll('button.cdot');
|
||||
for (var i = 0; i < btns.length; i++) {
|
||||
btns[i].className = btns[i].getAttribute('data-c') === name ? 'cdot on' : 'cdot';
|
||||
}
|
||||
}
|
||||
|
||||
async function applyBgImg(input) {
|
||||
var src = (input || '').trim();
|
||||
if (!src) {
|
||||
document.documentElement.style.setProperty('--bg-img', 'none');
|
||||
localStorage.removeItem('ha-bg-img');
|
||||
localStorage.removeItem('ha-bg-final');
|
||||
return;
|
||||
}
|
||||
var finalSrc = src;
|
||||
if (window.homeagent && window.homeagent.cacheBg) {
|
||||
try {
|
||||
var r = await window.homeagent.cacheBg(src);
|
||||
if (r && r.ok && r.file) finalSrc = r.file;
|
||||
else if (r && r.error && !r.useOriginal) toast(__('背景图加载失败: ','Bg load failed: ') + r.error, true);
|
||||
} catch (e) { toast(__('背景图加载失败: ','Bg load failed: ') + e.message, true); }
|
||||
}
|
||||
document.documentElement.style.setProperty('--bg-img', 'url("' + finalSrc.replace(/"/g, '\\"') + '")');
|
||||
if (/^data:/.test(src)) {
|
||||
localStorage.setItem('ha-bg-img', finalSrc);
|
||||
} else {
|
||||
localStorage.setItem('ha-bg-img', src);
|
||||
}
|
||||
localStorage.setItem('ha-bg-final', finalSrc);
|
||||
}
|
||||
|
||||
function setBgImgVar(finalSrc) {
|
||||
document.documentElement.style.setProperty('--bg-img', 'url("' + (finalSrc || '').replace(/"/g, '\\"') + '")');
|
||||
}
|
||||
|
||||
function pickBgFile() {
|
||||
var fi = document.getElementById('bg-file-input');
|
||||
if (!fi) {
|
||||
fi = document.createElement('input');
|
||||
fi.type = 'file';
|
||||
fi.id = 'bg-file-input';
|
||||
fi.accept = 'image/*';
|
||||
fi.style.display = 'none';
|
||||
fi.onchange = function () {
|
||||
var f = fi.files && fi.files[0];
|
||||
if (!f) return;
|
||||
var rd = new FileReader();
|
||||
rd.onload = function () { applyBgImg(rd.result); };
|
||||
rd.readAsDataURL(f);
|
||||
fi.value = '';
|
||||
};
|
||||
document.body.appendChild(fi);
|
||||
}
|
||||
fi.click();
|
||||
}
|
||||
|
||||
function applyBgBlur(n) {
|
||||
n = Math.max(0, Math.min(30, Number(n) || 0));
|
||||
document.documentElement.style.setProperty('--bg-blur', String(n));
|
||||
localStorage.setItem('ha-bg-blur', String(n));
|
||||
var v = document.getElementById('bg-blur-val');
|
||||
if (v) v.textContent = n + 'px';
|
||||
var r = document.getElementById('bg-blur-range');
|
||||
if (r) r.value = String(n);
|
||||
}
|
||||
|
||||
function toggleAppearance() {
|
||||
var pop = document.getElementById('palette-pop');
|
||||
if (!pop) return;
|
||||
var on = pop.classList.contains('on');
|
||||
if (!pop.querySelector('button.cdot')) {
|
||||
var cur = localStorage.getItem('ha-color') || 'sakura';
|
||||
var img = localStorage.getItem('ha-bg-img') || '';
|
||||
var blur = localStorage.getItem('ha-bg-blur') || '0';
|
||||
var dots = '';
|
||||
Object.keys(PALETTES_GUI).forEach(function (k) {
|
||||
dots += '<button class="cdot" data-c="' + k + '" title="' + k +
|
||||
'" style="background:' + PALETTES_GUI[k] +
|
||||
'" onclick="setColor(\'' + k + '\')"></button>';
|
||||
});
|
||||
pop.innerHTML =
|
||||
'<h4>' + __('主题色','Theme color') + '</h4><div>' + dots + '</div>' +
|
||||
'<h4 style="margin-top:8px">' + __('背景图片 URL','Background image URL') + '</h4>' +
|
||||
'<input type="text" id="bg-img-input" placeholder="https://...jpg / png" value="' + escHtml(img) + '">' +
|
||||
'<div class="pp-row"><button class="btn btn-ghost btn-sm" onclick="applyBgImg(document.getElementById(\'bg-img-input\').value)">' + __('应用','Apply') + '</button>' +
|
||||
'<button class="btn btn-ghost btn-sm" onclick="pickBgFile()">' + __('本地图片…','Local image…') + '</button>' +
|
||||
'<button class="btn btn-ghost btn-sm" onclick="applyBgImg(\'\')">' + __('清除','Clear') + '</button>' +
|
||||
'<span class="pp-val">' + __('模糊','Blur') + ' <input type="range" id="bg-blur-range" min="0" max="30" value="' + blur + '" oninput="applyBgBlur(this.value)">' +
|
||||
'<span id="bg-blur-val">' + blur + 'px</span></span></div>';
|
||||
setColor(cur);
|
||||
var rr = document.getElementById('bg-blur-range');
|
||||
if (rr) rr.value = blur;
|
||||
var vv = document.getElementById('bg-blur-val');
|
||||
if (vv) vv.textContent = blur + 'px';
|
||||
}
|
||||
pop.classList.toggle('on', !on);
|
||||
}
|
||||
|
||||
(function() {
|
||||
var saved = localStorage.getItem('ha-theme');
|
||||
setTheme(saved || 'dark');
|
||||
setTheme(saved || 'light');
|
||||
var finalSrc = localStorage.getItem('ha-bg-final');
|
||||
var img = localStorage.getItem('ha-bg-img');
|
||||
var blur = localStorage.getItem('ha-bg-blur');
|
||||
if (img) {
|
||||
if (finalSrc) setBgImgVar(finalSrc);
|
||||
else applyBgImg(img);
|
||||
}
|
||||
if (blur) applyBgBlur(blur);
|
||||
})();
|
||||
|
||||
// ===== Utility =====
|
||||
@ -191,9 +309,19 @@ async function api(p, o) {
|
||||
return cliMap(p, o);
|
||||
}
|
||||
var opts = o || {};
|
||||
var to = opts.timeout || 8000;
|
||||
var headers = { 'Content-Type': 'application/json', ...(opts.headers || {}) };
|
||||
if (state.currentConn.apiKey) headers['X-API-Key'] = state.currentConn.apiKey;
|
||||
var r = await fetch(state.currentConn.url + '/api/v1' + p, { ...opts, headers: headers });
|
||||
var ctl = new AbortController();
|
||||
var timer = setTimeout(function(){ ctl.abort() }, to);
|
||||
var r;
|
||||
try {
|
||||
r = await fetch(state.currentConn.url + '/api/v1' + p, { ...opts, headers: headers, signal: ctl.signal });
|
||||
} catch (e) {
|
||||
clearTimeout(timer);
|
||||
throw new Error(__('连接超时或失败','Timeout or connection failed'));
|
||||
}
|
||||
clearTimeout(timer);
|
||||
if (r.status === 401) throw new Error(__('认证失败','unauthorized'));
|
||||
if (opts.raw) return r;
|
||||
var ct = r.headers.get('content-type') || '';
|
||||
@ -220,6 +348,22 @@ function switchView(n) {
|
||||
|
||||
// ===== Tab Render Dispatch =====
|
||||
async function doRenderAll() {
|
||||
await refreshAll();
|
||||
}
|
||||
|
||||
function renderAll() {
|
||||
try { renderOverview() } catch(e) { console.error('renderOverview', e) }
|
||||
try { renderChat() } catch(e) { console.error('renderChat', e) }
|
||||
try { renderChatStarmap() } catch(e) { console.error('renderChatStarmap', e) }
|
||||
try { renderPlugins() } catch(e) { console.error('renderPlugins', e) }
|
||||
try { renderKernel() } catch(e) { console.error('renderKernel', e) }
|
||||
try { renderOneSettings() } catch(e) { console.error('renderOneSettings', e) }
|
||||
try { renderAdapters() } catch(e) { console.error('renderAdapters', e) }
|
||||
applyI18n();
|
||||
refreshAll();
|
||||
}
|
||||
|
||||
async function refreshAll() {
|
||||
try { var s = await api('/status'); state.status = s; state.startedAt = s.startedAt ? new Date(s.startedAt).getTime() : null; updateConnIndicator() } catch(e) {}
|
||||
try { state.kernel = await api('/kernel') } catch(e) {}
|
||||
try {
|
||||
@ -228,22 +372,7 @@ async function doRenderAll() {
|
||||
state.meta = s.meta || {};
|
||||
state.settingsPlugins = s.plugins || ['core'];
|
||||
state.pluginMeta = s.plugin_meta || {};
|
||||
} catch(e) {}
|
||||
try { state.installedPlugins = await api('/plugins') } catch(e) {}
|
||||
try { await loadTerminals() } catch(e) {}
|
||||
try { await loadCmdHistory() } catch(e) {}
|
||||
renderAll();
|
||||
}
|
||||
|
||||
async function renderAll() {
|
||||
try { var s = await api('/status'); state.status = s; state.startedAt = s.startedAt ? new Date(s.startedAt).getTime() : null } catch(e) {}
|
||||
try { state.kernel = await api('/kernel') } catch(e) {}
|
||||
try {
|
||||
var s = await api('/settings');
|
||||
state.settings = s.settings || {};
|
||||
state.meta = s.meta || {};
|
||||
state.settingsPlugins = s.plugins || ['core'];
|
||||
state.pluginMeta = s.plugin_meta || {};
|
||||
state.disabledPlugins = s.disabled_plugins || [];
|
||||
} catch(e) {}
|
||||
try { state.installedPlugins = await api('/plugins') } catch(e) {}
|
||||
try { await loadTerminals() } catch(e) {}
|
||||
@ -351,7 +480,7 @@ function buildChatLayout() {
|
||||
html += '<div class="chat-panel active" id="chat-panel-chat"><div class="chat-main">';
|
||||
html += '<div class="card"><h2>' + __('对话','Chat') + ' <span id="chat-stage" class="badge" style="font-size:10px;font-weight:400;display:none">' + escHtml(state.chatStage || '') + '</span></h2><div class="chat-messages" id="chat-msgs">';
|
||||
if (state.messages.length === 0) {
|
||||
html += '<div class="empty-state" style="flex:1;display:flex;align-items:center;justify-content:center"><p>' + __('开始对话以测试 Agent 回复','Start a conversation to test Agent replies') + '</p></div>';
|
||||
html += '<div class="empty-state" style="flex:1;display:flex;align-items:center;justify-content:center"><p>' + __('开始与您的 HomeAgent 聊天吧','Start chatting with your HomeAgent') + '</p></div>';
|
||||
}
|
||||
html += '</div>'
|
||||
+ '<div class="chat-input-row">'
|
||||
@ -451,7 +580,7 @@ function renderChat() {
|
||||
}
|
||||
var html = '';
|
||||
if (msgs.length === 0) {
|
||||
html = '<div class="empty-state" style="flex:1;display:flex;align-items:center;justify-content:center"><p>' + __('开始对话以测试 Agent 回复','Start a conversation to test Agent replies') + '</p></div>';
|
||||
html = '<div class="empty-state" style="flex:1;display:flex;align-items:center;justify-content:center"><p>' + __('开始与您的 HomeAgent 聊天吧','Start chatting with your HomeAgent') + '</p></div>';
|
||||
} else {
|
||||
msgs.forEach(function(m, i) {
|
||||
var role = m.role || 'user';
|
||||
@ -839,7 +968,7 @@ async function sendChat() {
|
||||
btn.textContent = '';
|
||||
rerenderChat();
|
||||
try {
|
||||
var r = await api('/chat', { method: 'POST', body: JSON.stringify({ message: text }) });
|
||||
var r = await api('/chat', { method: 'POST', body: JSON.stringify({ message: text }), timeout: 120000 });
|
||||
state.chatStage = '';
|
||||
var last = state.messages[state.messages.length - 1];
|
||||
console.log('[sendChat] POST returned, last msg:', last ? {role:last.role, _streaming:last._streaming, _final:last._final, tool_calls:last.tool_calls?.length, content_len:last.content?.length} : null);
|
||||
@ -1072,16 +1201,42 @@ function renderPlugins() {
|
||||
+ '<div><input type="file" id="plugin-file" accept=".hmap" style="display:inline;width:auto" onchange="installPluginFile(this.files[0])">'
|
||||
+ '<label for="plugin-file" class="btn btn-ghost" style="cursor:pointer">' + __('选择 .hmap 文件上传','Upload .hmap file') + '</label></div></div>';
|
||||
var installedNames = (state.installedPlugins || []).map(function(p) { return p.name });
|
||||
var disabledNames = {};
|
||||
(state.disabledPlugins || []).forEach(function(d) { disabledNames[d.name] = d; });
|
||||
html += '<div class="card"><h2>' + __('已加载插件','Loaded Plugins') + ' (' + plugins.length + ')</h2>';
|
||||
if (plugins.length === 0) {
|
||||
if (plugins.length === 0 && (state.disabledPlugins || []).length === 0) {
|
||||
html += '<div class="empty-state"><p>' + __('暂无已加载插件','No loaded plugins') + '</p></div>';
|
||||
} else {
|
||||
html += '<table><tr><th>' + __('名称','Name') + '</th><th>' + __('状态','Status') + '</th><th>' + __('操作','Actions') + '</th></tr>';
|
||||
plugins.forEach(function(p) {
|
||||
var isExternal = installedNames.indexOf(p.name) >= 0;
|
||||
html += '<tr><td>' + escHtml(p.name) + '</td>'
|
||||
+ '<td><span class="badge badge-green">' + __('已加载','Loaded') + '</span></td>'
|
||||
+ '<td>' + (isExternal ? '<button class="btn btn-sm btn-danger" onclick="removePlugin(\'' + escHtml(p.name) + '\')">' + __('卸载','Unload') + '</button>' : '<span class="badge badge-blue">' + __('内置','Built-in') + '</span>') + '</td></tr>';
|
||||
var allNames = {};
|
||||
plugins.forEach(function(p) { allNames[p.name] = true; });
|
||||
(state.disabledPlugins || []).forEach(function(d) { allNames[d.name] = true; });
|
||||
Object.keys(allNames).sort().forEach(function(name) {
|
||||
var loaded = plugins.some(function(p) { return p.name === name });
|
||||
var isDisabled = !!disabledNames[name];
|
||||
var isExternal = installedNames.indexOf(name) >= 0;
|
||||
var statusHtml = loaded && !isDisabled
|
||||
? '<span class="badge badge-green">' + __('已加载','Loaded') + '</span>'
|
||||
: loaded && isDisabled
|
||||
? '<span class="badge badge-yellow">' + __('运行中(禁用待生效)','Running (disable pending)') + '</span>'
|
||||
: isDisabled
|
||||
? '<span class="badge badge-red">' + __('已禁用','Disabled') + '</span>'
|
||||
: '<span class="badge">' + __('未加载','Not Loaded') + '</span>';
|
||||
var actionsHtml = '';
|
||||
if (loaded && !isDisabled) {
|
||||
actionsHtml += '<button class="btn btn-sm btn-warning" onclick="disablePlugin(\'' + escHtml(name) + '\')" style="margin-right:4px">' + __('禁用','Disable') + '</button>';
|
||||
}
|
||||
if (isDisabled) {
|
||||
actionsHtml += '<button class="btn btn-sm btn-primary" onclick="enablePlugin(\'' + escHtml(name) + '\')" style="margin-right:4px">' + __('启用','Enable') + '</button>';
|
||||
}
|
||||
if (loaded && isExternal) {
|
||||
actionsHtml += '<button class="btn btn-sm btn-danger" onclick="removePlugin(\'' + escHtml(name) + '\')">' + __('卸载','Unload') + '</button>';
|
||||
} else if (loaded) {
|
||||
actionsHtml += '<span class="badge badge-blue">' + __('内置','Built-in') + '</span>';
|
||||
}
|
||||
html += '<tr><td>' + escHtml(name) + '</td>'
|
||||
+ '<td>' + statusHtml + '</td>'
|
||||
+ '<td>' + actionsHtml + '</td></tr>';
|
||||
});
|
||||
html += '</table>';
|
||||
}
|
||||
@ -1167,6 +1322,32 @@ async function removePlugin(name) {
|
||||
} catch(e) { toast(__('卸载失败: ','Unload failed: ') + e.message, true) }
|
||||
}
|
||||
|
||||
async function disablePlugin(name) {
|
||||
if (name === 'webui') {
|
||||
var r = confirm(__('禁用 WebUI 后将无法通过 URL:端口访问此管理面板,若要重新启用需要通过 CLI 命令 /plugin enable webui 恢复。\n\n确定要禁用吗?','Disabling WebUI will make this management panel inaccessible via URL:port. To re-enable, use CLI command /plugin enable webui.\n\nAre you sure?'));
|
||||
if (!r) return;
|
||||
}
|
||||
try {
|
||||
await api('/plugins/' + encodeURIComponent(name) + '/disable', { method: 'POST' });
|
||||
toast(__('已禁用: ','Disabled: ') + name);
|
||||
state.kernel = await api('/kernel');
|
||||
var s = await api('/settings');
|
||||
state.disabledPlugins = s.disabled_plugins || [];
|
||||
renderPlugins();
|
||||
} catch(e) { toast(__('禁用失败: ','Disable failed: ') + e.message, true) }
|
||||
}
|
||||
|
||||
async function enablePlugin(name) {
|
||||
try {
|
||||
await api('/plugins/' + encodeURIComponent(name) + '/enable', { method: 'POST' });
|
||||
toast(__('已启用: ','Enabled: ') + name);
|
||||
state.kernel = await api('/kernel');
|
||||
var s = await api('/settings');
|
||||
state.disabledPlugins = s.disabled_plugins || [];
|
||||
renderPlugins();
|
||||
} catch(e) { toast(__('启用失败: ','Enable failed: ') + e.message, true) }
|
||||
}
|
||||
|
||||
async function reloadPlugins() {
|
||||
try {
|
||||
var r = await api('/plugins/reload', { method: 'POST' });
|
||||
@ -1703,6 +1884,7 @@ async function deleteAdapter(name) {
|
||||
state.connections = data.connections || [];
|
||||
if (data.currentId) state.currentConn = state.connections.find(function(c) { return c.id === data.currentId }) || null;
|
||||
if (state.currentConn) {
|
||||
await syncConnAuth();
|
||||
connectSSE();
|
||||
await loadChatHistory();
|
||||
doRenderAll();
|
||||
@ -1768,6 +1950,24 @@ function renderConnSection() {
|
||||
+ '<option value="cli">CLI (unix socket)</option></select>'
|
||||
+ '<div id="conn-addr-webui"><label>' + __('地址','URL') + '</label><input id="conn-url" placeholder="http://localhost:18080"></div>'
|
||||
+ '<div id="conn-addr-cli" style="display:none"><label>' + __('Socket 路径','Socket Path') + '</label><input id="conn-sock" placeholder="C:\\path\\to\\cli.sock"></div>'
|
||||
+ '<div id="conn-auth-webui">'
|
||||
+ '<label>' + __('WebUI 账号','WebUI Username') + ' <span style="color:var(--text-muted);font-weight:400">(' + __('自动登录第二层网关','auto-login 2nd gateway') + ')</span></label>'
|
||||
+ '<input id="conn-user" placeholder="admin">'
|
||||
+ '<label>' + __('密码','Password') + '</label><input id="conn-pass" type="password" placeholder="••••">'
|
||||
+ '<div style="display:flex;gap:6px;align-items:center;padding:4px 0 8px;color:var(--text-secondary);font-size:12px">'
|
||||
+ '<input type="checkbox" id="conn-gw" onchange="toggleGwFields()" style="width:auto;margin:0">'
|
||||
+ '<label for="conn-gw" style="margin:0;font-size:12px">' + __('经过总网关(可选)','Via gateway (optional)') + '</label>'
|
||||
+ '</div>'
|
||||
+ '<div id="conn-gw-fields" style="display:none">'
|
||||
+ '<label>' + __('总网关 Cookie','Gateway Cookie') + ' <span style="color:var(--text-muted);font-weight:400">(' + __('登录窗口自动抓取','auto-captured by login window') + ')</span></label>'
|
||||
+ '<textarea id="conn-cookie" rows="2" placeholder="sl-session=...; gateway_session=..." style="min-height:40px"></textarea>'
|
||||
+ '<div style="display:flex;gap:6px;align-items:center;margin-bottom:10px">'
|
||||
+ '<button class="btn btn-ghost btn-sm" onclick="openLoginWindow()">' + __('打开登录窗口(自动抓取 Cookie)','Open login window (auto-grab cookies)') + '</button>'
|
||||
+ '</div>'
|
||||
+ '</div>'
|
||||
+ '<label>' + __('额外请求头 JSON','Extra Headers JSON') + ' <span style="color:var(--text-muted);font-weight:400">(' + __('可选','optional') + ')</span></label>'
|
||||
+ '<input id="conn-headers" placeholder=\'{"X-Api-Key":"..."}\'>'
|
||||
+ '</div>'
|
||||
+ '<label>' + __('API 密钥','API Key') + ' <span style="color:var(--text-muted);font-weight:400">(' + __('可选','optional') + ')</span></label>'
|
||||
+ '<input id="conn-key" type="password" placeholder="sk-...">'
|
||||
+ '<div class="conn-form-actions">'
|
||||
@ -1787,6 +1987,69 @@ function toggleConnType() {
|
||||
var t = document.getElementById('conn-type').value;
|
||||
document.getElementById('conn-addr-webui').style.display = t === 'cli' ? 'none' : 'block';
|
||||
document.getElementById('conn-addr-cli').style.display = t === 'cli' ? 'block' : 'none';
|
||||
document.getElementById('conn-auth-webui').style.display = t === 'cli' ? 'none' : 'block';
|
||||
toggleGwFields();
|
||||
}
|
||||
|
||||
function toggleGwFields() {
|
||||
var gw = document.getElementById('conn-gw');
|
||||
var fields = document.getElementById('conn-gw-fields');
|
||||
if (gw && fields) fields.style.display = gw.checked ? 'block' : 'none';
|
||||
}
|
||||
|
||||
async function openLoginWindow(useForm) {
|
||||
if (useForm === undefined) useForm = true;
|
||||
var url, us, ps;
|
||||
if (useForm) {
|
||||
url = document.getElementById('conn-url').value.trim().replace(/\/+$/, '');
|
||||
us = document.getElementById('conn-user').value.trim();
|
||||
ps = document.getElementById('conn-pass').value;
|
||||
} else {
|
||||
url = arguments[1];
|
||||
us = arguments[2] || '';
|
||||
ps = arguments[3] || '';
|
||||
}
|
||||
if (!url) { toast(__('请先填写地址','Set URL first'), true); return; }
|
||||
var handled = false;
|
||||
window.homeagent.webui.onLoginResult(function(d) {
|
||||
if (handled) return; handled = true;
|
||||
if (window.homeagent && window.homeagent.log) window.homeagent.log('r: login-result ok=' + (d && d.ok) + ' count=' + (d && d.count));
|
||||
if (_loginWaitRes) {
|
||||
var r = _loginWaitRes; _loginWaitRes = null;
|
||||
r(d);
|
||||
return;
|
||||
}
|
||||
if (!d || !d.ok) {
|
||||
toast(__('未取得 Cookie: ','No cookies: ') + ((d && d.error) || 'unknown'), true);
|
||||
return;
|
||||
}
|
||||
var form = document.getElementById('conn-form');
|
||||
var editing = form && form.style.display === 'block';
|
||||
if (editing) {
|
||||
document.getElementById('conn-cookie').value = d.cookie || '';
|
||||
toast(__('已取得 ','Got ') + (d.count || 0) + __(' 个 Cookie,点保存生效',' cookies, click Save to apply'));
|
||||
return;
|
||||
}
|
||||
if (state.currentConn && d.url.replace(/\/+$/, '') === state.currentConn.url) {
|
||||
window.homeagent.connections.update(state.currentConn.id, { cookie: d.cookie || '' })
|
||||
.then(function (data) {
|
||||
state.connections = data.connections;
|
||||
state.currentConn = data.connections.find(function (c) { return c.id === data.currentId }) || state.currentConn;
|
||||
updateConnIndicator();
|
||||
return syncConnAuth();
|
||||
})
|
||||
.then(function () {
|
||||
toast(__('总网关 Cookie 已自动生效','Gateway cookie applied automatically'));
|
||||
if (state.messages.length === 0) loadChatHistory().then(function(){ rerenderChat() }).catch(function(){});
|
||||
return null;
|
||||
})
|
||||
.catch(function (e) { toast(__('应用 Cookie 失败: ','Apply cookie failed: ') + e.message, true); });
|
||||
} else {
|
||||
toast(__('已获得 Cookie(请切换到对应连接后保存)','Cookies acquired (switch to the matching connection to save)'), false);
|
||||
}
|
||||
});
|
||||
var r = await window.homeagent.webui.openLogin(url, us, ps);
|
||||
if (!r || !r.ok) toast(__('无法打开登录窗口: ','Cannot open login window: ') + ((r && r.error) || ''), true);
|
||||
}
|
||||
|
||||
function showConnForm() {
|
||||
@ -1796,6 +2059,12 @@ function showConnForm() {
|
||||
document.getElementById('conn-url').value = 'http://localhost:18080';
|
||||
document.getElementById('conn-sock').value = '';
|
||||
document.getElementById('conn-key').value = '';
|
||||
document.getElementById('conn-user').value = '';
|
||||
document.getElementById('conn-pass').value = '';
|
||||
document.getElementById('conn-cookie').value = '';
|
||||
document.getElementById('conn-headers').value = '';
|
||||
document.getElementById('conn-gw').checked = false;
|
||||
toggleGwFields();
|
||||
document.getElementById('conn-type').value = 'webui';
|
||||
toggleConnType();
|
||||
document.getElementById('conn-form').style.display = 'block';
|
||||
@ -1812,6 +2081,12 @@ function editConnection(id, e) {
|
||||
document.getElementById('conn-url').value = c.url || 'http://localhost:18080';
|
||||
document.getElementById('conn-sock').value = c.socketPath || '';
|
||||
document.getElementById('conn-key').value = c.apiKey;
|
||||
document.getElementById('conn-user').value = c.username || '';
|
||||
document.getElementById('conn-pass').value = c.password || '';
|
||||
document.getElementById('conn-cookie').value = c.cookie || '';
|
||||
document.getElementById('conn-headers').value = c.headers || '';
|
||||
document.getElementById('conn-gw').checked = !!(c.gateway || c.cookie);
|
||||
toggleGwFields();
|
||||
document.getElementById('conn-type').value = c.type === 'cli' ? 'cli' : 'webui';
|
||||
toggleConnType();
|
||||
document.getElementById('conn-form').style.display = 'block';
|
||||
@ -1830,6 +2105,7 @@ async function selectConnection(id) {
|
||||
state.connections = data.connections;
|
||||
state.messages = [];
|
||||
updateConnIndicator();
|
||||
await syncConnAuth();
|
||||
connectSSE();
|
||||
await loadChatHistory();
|
||||
doRenderAll();
|
||||
@ -1847,21 +2123,60 @@ async function deleteConnection(id, e) {
|
||||
state.currentConn = data.currentId ? state.connections.find(function(c) { return c.id === data.currentId }) : null;
|
||||
if (wasCurrent && state.eventSource) { state.eventSource.close(); state.eventSource = null; }
|
||||
if (state.currentConn) {
|
||||
updateConnIndicator(); doRenderAll(); connectSSE();
|
||||
updateConnIndicator(); doRenderAll(); syncConnAuth(); connectSSE();
|
||||
} else {
|
||||
updateConnIndicator();
|
||||
if (window.homeagent.webui) await window.homeagent.webui.setAuth('', '', '', '', '');
|
||||
}
|
||||
renderConnSection();
|
||||
}
|
||||
|
||||
var editingConnId = null;
|
||||
var _loginWaitRes = null;
|
||||
|
||||
function waitLogin() {
|
||||
return new Promise(function (res) { _loginWaitRes = res; });
|
||||
}
|
||||
|
||||
async function syncConnAuth() {
|
||||
var c = state.currentConn;
|
||||
if (!c || c.type !== 'webui' || !c.url) {
|
||||
if (window.homeagent.webui) await window.homeagent.webui.setAuth('', '', '', '', '');
|
||||
return true;
|
||||
}
|
||||
var headers = {};
|
||||
if (c.headers) { try { headers = JSON.parse(c.headers) || {} } catch (e) {} }
|
||||
var r = await window.homeagent.webui.setAuth(c.url, c.cookie || '', headers, c.username || '', c.password || '');
|
||||
if (r && r.ok === false) {
|
||||
toast(__('自动登录 WebUI 失败(已忽略,继续使用现有 Cookie): ','WebUI auto-login failed (ignored): ') + r.error, true);
|
||||
if (c.gateway && !c.cookie) {
|
||||
setTimeout(function () { openLoginWindow(false, c.url, c.username || '', c.password || '') }, 900);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (c.gateway && c.cookie && r && r.ok !== false) {
|
||||
toast(__('总网关 Cookie 已生效','Gateway cookie active'));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function saveConnForm() {
|
||||
if (window.homeagent && window.homeagent.log) window.homeagent.log('save: start');
|
||||
var name = document.getElementById('conn-name').value.trim();
|
||||
var ctype = document.getElementById('conn-type').value;
|
||||
var url = document.getElementById('conn-url').value.trim().replace(/\/+$/, '');
|
||||
var sock = document.getElementById('conn-sock').value.trim();
|
||||
var apiKey = document.getElementById('conn-key').value.trim();
|
||||
var username = document.getElementById('conn-user').value.trim();
|
||||
var password = document.getElementById('conn-pass').value;
|
||||
var gwEnabled = !!(document.getElementById('conn-gw') && document.getElementById('conn-gw').checked);
|
||||
var cookie = gwEnabled ? document.getElementById('conn-cookie').value.trim() : '';
|
||||
var headersRaw = document.getElementById('conn-headers').value.trim();
|
||||
var headers = '';
|
||||
if (headersRaw) {
|
||||
try { JSON.parse(headersRaw); headers = headersRaw; }
|
||||
catch (e) { toast(__('额外请求头不是合法 JSON','Extra headers not valid JSON'), true); return; }
|
||||
}
|
||||
if (ctype === 'cli') {
|
||||
if (!name || !sock) { toast(__('名称和 Socket 路径不能为空','Name and Socket Path required'), true); return; }
|
||||
} else {
|
||||
@ -1878,8 +2193,38 @@ async function saveConnForm() {
|
||||
testBtn.textContent = __('保存','Save'); testBtn.disabled = false; return;
|
||||
}
|
||||
} else {
|
||||
var testR = await fetch(url + '/api/v1/status', { headers: apiKey ? { 'X-API-Key': apiKey } : {} });
|
||||
if (!testR.ok) { toast(__('连接测试失败: HTTP ','Connection test failed: HTTP ') + testR.status, true); testBtn.textContent = __('保存','Save'); testBtn.disabled = false; return; }
|
||||
if (window.homeagent && window.homeagent.webui) {
|
||||
if (gwEnabled && !cookie) {
|
||||
testBtn.textContent = __('请在登录窗口完成网关登录…','Complete gateway login…');
|
||||
testBtn.disabled = true;
|
||||
openLoginWindow(false, url, username, password);
|
||||
var lg = await waitLogin();
|
||||
if (!lg || !lg.ok) {
|
||||
toast(__('网关登录未完成,已取消保存','Gateway login incomplete, save cancelled') + ((lg && lg.error) ? ': ' + lg.error : ''), true);
|
||||
testBtn.textContent = __('保存','Save'); testBtn.disabled = false;
|
||||
return;
|
||||
}
|
||||
cookie = lg.cookie || '';
|
||||
}
|
||||
var tHeaders = {};
|
||||
if (headersRaw) { try { tHeaders = JSON.parse(headersRaw) } catch (e) {} }
|
||||
if (window.homeagent.log) window.homeagent.log('save: setAuth url=' + url + ' gw=' + gwEnabled + ' cookieLen=' + cookie.length);
|
||||
try {
|
||||
await window.homeagent.webui.setAuth(url, cookie, tHeaders, username, password);
|
||||
} catch (e) { toast(__('应用认证失败: ','Apply auth failed: ') + e.message, true); }
|
||||
}
|
||||
if (window.homeagent.log) window.homeagent.log('save: testing ' + url + '/api/v1/status');
|
||||
var testR;
|
||||
try {
|
||||
testR = await fetch(url + '/api/v1/status', { headers: apiKey ? { 'X-API-Key': apiKey } : {} });
|
||||
} catch (e) {
|
||||
if (window.homeagent.log) window.homeagent.log('save: fetch error: ' + e.message);
|
||||
toast(__('无法连接到 ','Cannot connect to ') + url + ': ' + e.message, true);
|
||||
testBtn.textContent = __('保存','Save'); testBtn.disabled = false;
|
||||
return;
|
||||
}
|
||||
if (window.homeagent.log) window.homeagent.log('save: status=' + testR.status);
|
||||
if (!testR.ok) { toast(__('连接测试失败: HTTP ','Connection test failed: HTTP ') + testR.status + '(' + (await testR.text()).slice(0, 120) + ')', true); testBtn.textContent = __('保存','Save'); testBtn.disabled = false; return; }
|
||||
}
|
||||
} catch(e) {
|
||||
toast(__('无法连接到 ','Cannot connect to ') + (ctype === 'cli' ? sock : url) + ': ' + e.message, true);
|
||||
@ -1888,7 +2233,7 @@ async function saveConnForm() {
|
||||
testBtn.textContent = __('保存','Save'); testBtn.disabled = false;
|
||||
var connData = ctype === 'cli'
|
||||
? { name: name, type: 'cli', socketPath: sock, url: '', apiKey: apiKey }
|
||||
: { name: name, type: 'webui', url: url, apiKey: apiKey };
|
||||
: { name: name, type: 'webui', url: url, apiKey: apiKey, username: username, password: password, cookie: cookie, headers: headers, gateway: gwEnabled };
|
||||
var data;
|
||||
if (editingConnId) {
|
||||
data = await window.homeagent.connections.update(editingConnId, connData);
|
||||
@ -1900,6 +2245,7 @@ async function saveConnForm() {
|
||||
var switched = !!cur && (!state.currentConn || state.currentConn.id !== cur.id);
|
||||
if (cur) {
|
||||
state.currentConn = cur;
|
||||
await syncConnAuth();
|
||||
if (switched) {
|
||||
if (state.eventSource) { state.eventSource.close(); state.eventSource = null; }
|
||||
state.messages = [];
|
||||
|
||||
Reference in New Issue
Block a user