myagent-ai 1.23.19 → 1.23.21
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/chatbot/whatsapp_bot.py +0 -0
- package/chatbot/whatsapp_bridge/bridge.mjs +0 -0
- package/chatbot/whatsapp_bridge/package.json +0 -0
- package/core/stt.py +0 -0
- package/core/tool_dispatcher.py +0 -0
- package/core/web_control.py +0 -0
- package/data/novnc/lib/base64.js +0 -0
- package/data/novnc/lib/crypto/aes.js +0 -0
- package/data/novnc/lib/crypto/bigint.js +0 -0
- package/data/novnc/lib/crypto/crypto.js +0 -0
- package/data/novnc/lib/crypto/des.js +0 -0
- package/data/novnc/lib/crypto/dh.js +0 -0
- package/data/novnc/lib/crypto/md5.js +0 -0
- package/data/novnc/lib/crypto/rsa.js +0 -0
- package/data/novnc/lib/decoders/copyrect.js +0 -0
- package/data/novnc/lib/decoders/hextile.js +0 -0
- package/data/novnc/lib/decoders/jpeg.js +0 -0
- package/data/novnc/lib/decoders/raw.js +0 -0
- package/data/novnc/lib/decoders/rre.js +0 -0
- package/data/novnc/lib/decoders/tight.js +0 -0
- package/data/novnc/lib/decoders/tightpng.js +0 -0
- package/data/novnc/lib/decoders/zrle.js +0 -0
- package/data/novnc/lib/deflator.js +0 -0
- package/data/novnc/lib/display.js +0 -0
- package/data/novnc/lib/encodings.js +0 -0
- package/data/novnc/lib/inflator.js +0 -0
- package/data/novnc/lib/input/domkeytable.js +0 -0
- package/data/novnc/lib/input/fixedkeys.js +0 -0
- package/data/novnc/lib/input/gesturehandler.js +0 -0
- package/data/novnc/lib/input/keyboard.js +0 -0
- package/data/novnc/lib/input/keysym.js +0 -0
- package/data/novnc/lib/input/keysymdef.js +0 -0
- package/data/novnc/lib/input/util.js +0 -0
- package/data/novnc/lib/input/vkeys.js +0 -0
- package/data/novnc/lib/input/xtscancodes.js +0 -0
- package/data/novnc/lib/ra2.js +0 -0
- package/data/novnc/lib/rfb.js +0 -0
- package/data/novnc/lib/util/browser.js +0 -0
- package/data/novnc/lib/util/cursor.js +0 -0
- package/data/novnc/lib/util/element.js +0 -0
- package/data/novnc/lib/util/events.js +0 -0
- package/data/novnc/lib/util/eventtarget.js +0 -0
- package/data/novnc/lib/util/int.js +0 -0
- package/data/novnc/lib/util/logging.js +0 -0
- package/data/novnc/lib/util/strings.js +0 -0
- package/data/novnc/lib/vendor/pako/lib/utils/common.js +0 -0
- package/data/novnc/lib/vendor/pako/lib/zlib/adler32.js +0 -0
- package/data/novnc/lib/vendor/pako/lib/zlib/constants.js +0 -0
- package/data/novnc/lib/vendor/pako/lib/zlib/crc32.js +0 -0
- package/data/novnc/lib/vendor/pako/lib/zlib/deflate.js +0 -0
- package/data/novnc/lib/vendor/pako/lib/zlib/gzheader.js +0 -0
- package/data/novnc/lib/vendor/pako/lib/zlib/inffast.js +0 -0
- package/data/novnc/lib/vendor/pako/lib/zlib/inflate.js +0 -0
- package/data/novnc/lib/vendor/pako/lib/zlib/inftrees.js +0 -0
- package/data/novnc/lib/vendor/pako/lib/zlib/messages.js +0 -0
- package/data/novnc/lib/vendor/pako/lib/zlib/trees.js +0 -0
- package/data/novnc/lib/vendor/pako/lib/zlib/zstream.js +0 -0
- package/data/novnc/lib/websock.js +0 -0
- package/package.json +1 -1
- package/scripts/cli.py +0 -0
- package/skills/agent_tool_skill.py +0 -0
- package/web/ui/admin/admin-agents.js +570 -0
- package/web/ui/admin/admin-core.js +322 -0
- package/web/ui/admin/admin-dashboard.js +153 -0
- package/web/ui/admin/admin-executor.js +67 -0
- package/web/ui/admin/admin-files.js +81 -0
- package/web/ui/admin/admin-llm.js +190 -0
- package/web/ui/admin/admin-logs.js +69 -0
- package/web/ui/admin/admin-memory.js +91 -0
- package/web/ui/admin/admin-org.js +283 -0
- package/web/ui/admin/admin-permissions.js +147 -0
- package/web/ui/admin/admin-platforms.js +221 -0
- package/web/ui/admin/admin-sessions.js +182 -0
- package/web/ui/admin/admin-skills.js +217 -0
- package/web/ui/admin/admin-system.js +154 -0
- package/web/ui/admin/admin-tasks.js +131 -0
- package/web/ui/chat/chat_main.js +292 -304
- package/web/ui/chat/flow_engine.js +96 -41
- package/web/ui/index.html +15 -2776
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// ========== LLM ==========
|
|
2
|
+
(function(){
|
|
3
|
+
try {
|
|
4
|
+
// ========== LLM ==========
|
|
5
|
+
async function renderLLM(){
|
|
6
|
+
const [u,models]=await Promise.all([api('/api/llm/usage'),api('/api/models')]);
|
|
7
|
+
// 更新模型缓存
|
|
8
|
+
allModelsCache=Array.isArray(models)?models:[];
|
|
9
|
+
let html='';
|
|
10
|
+
// 用量统计
|
|
11
|
+
html+=`<div class="card"><h3>用量统计</h3><div class="grid grid-4">
|
|
12
|
+
<div class="stat"><div class="label">调用次数</div><div class="value">${u.call_count||0}</div></div>
|
|
13
|
+
<div class="stat"><div class="label">Prompt Tokens</div><div class="value">${u.total_prompt_tokens||0}</div></div>
|
|
14
|
+
<div class="stat"><div class="label">Completion Tokens</div><div class="value">${u.total_completion_tokens||0}</div></div>
|
|
15
|
+
<div class="stat"><div class="label">估算费用</div><div class="value">$${(u.total_cost_usd||0).toFixed(4)}</div></div></div></div>`;
|
|
16
|
+
// 模型库列表
|
|
17
|
+
const modelList=models||[];
|
|
18
|
+
const enabledModels=modelList.filter(m=>m.enabled);
|
|
19
|
+
html+=`<div class="card"><div class="flex justify-between items-center mb-16">
|
|
20
|
+
<h3 style="margin:0">模型库 (${modelList.length})</h3>
|
|
21
|
+
<div class="flex gap-8">
|
|
22
|
+
<span class="badge badge-green">${enabledModels.length} 已启用</span>
|
|
23
|
+
<button class="btn btn-sm btn-primary" onclick="showAddModelModal()">+ 添加模型</button>
|
|
24
|
+
</div></div>`;
|
|
25
|
+
if(!modelList.length){
|
|
26
|
+
html+='<div class="empty">暂无自定义模型,点击上方按钮添加。</div>';
|
|
27
|
+
}else{
|
|
28
|
+
html+='<div class="table-wrap"><table style="font-size:12px"><tr><th>ID</th><th>名称</th><th>Provider</th><th>模型</th><th>上下文</th><th>输入</th><th>推理</th><th>兜底</th><th>状态</th><th>操作</th></tr>';
|
|
29
|
+
const providerColors={openai:'badge-green',anthropic:'badge-yellow',ollama:'badge-purple',zhipu:'badge-blue',custom:'badge-red',deepseek:'badge-blue',moonshot:'badge-purple',qwen:'badge-yellow',modelscope:'badge-purple'};
|
|
30
|
+
for(const m of modelList){
|
|
31
|
+
const badgeClass=providerColors[m.provider]||'badge-green';
|
|
32
|
+
const inputModes=m.input_modes?m.input_modes.join(','):'text';
|
|
33
|
+
const isFallback=m.is_global_fallback!==false;
|
|
34
|
+
html+=`<tr>
|
|
35
|
+
<td><code>${escHtml(m.id)}</code></td>
|
|
36
|
+
<td>${escHtml(m.name||'')}</td>
|
|
37
|
+
<td><span class="badge ${badgeClass}">${escHtml(m.provider||'')}</span></td>
|
|
38
|
+
<td style="max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${escHtml(m.model||'')}">${escHtml(m.model||'')}</td>
|
|
39
|
+
<td>${(m.context_window||128000).toLocaleString()}</td>
|
|
40
|
+
<td>${escHtml(inputModes)}</td>
|
|
41
|
+
<td>${m.reasoning?'<span class="badge badge-purple">是</span>':'-'}</td>
|
|
42
|
+
<td><span style="cursor:help" title="作为保障系统运行的最终兜底模型">${isFallback?'<span class="badge badge-green">☑</span>':'<span class="badge badge-red">☐</span>'}</span></td>
|
|
43
|
+
<td>${m.enabled?'<span class="badge badge-green">启用</span>':'<span class="badge badge-red">禁用</span>'}</td>
|
|
44
|
+
<td><button class="btn btn-sm btn-ghost" onclick="showEditModelModal(encodeURIComponent('${escHtml(m.id)}'))">编辑</button>
|
|
45
|
+
<button class="btn btn-sm btn-success" onclick="testModel(encodeURIComponent('${escHtml(m.id)}'))">测试</button>
|
|
46
|
+
<button class="btn btn-sm btn-danger" onclick="deleteModel('${escHtml(m.id)}','${escHtml(String(m.name||'').replace(/'/g,"\\'"))}')">删除</button></td></tr>`;
|
|
47
|
+
}
|
|
48
|
+
html+='</table></div>';
|
|
49
|
+
}
|
|
50
|
+
html+='</div>';
|
|
51
|
+
$('content').innerHTML=html;
|
|
52
|
+
}
|
|
53
|
+
// saveLLM / testLLM / showTestCode / hideTestCode 已移除(全局模型配置卡片已删除)
|
|
54
|
+
async function testModel(modelId){
|
|
55
|
+
modelId=decodeURIComponent(modelId);
|
|
56
|
+
const model=allModelsCache.find(m=>m.id===modelId);
|
|
57
|
+
if(!model){showToast('模型不存在','danger');return;}
|
|
58
|
+
const btn=event.target;btn.textContent='测试中...';btn.disabled=true;
|
|
59
|
+
// 直接发送模型自身配置,使用独立的测试接口,不经过任何兜底/fallback逻辑
|
|
60
|
+
// [v1.16.18] 传递 input_modes,让后端测试对应的多模态能力
|
|
61
|
+
const payload={model:model.model,base_url:model.base_url,provider:model.provider,temperature:model.temperature||0.1,api_key:model.api_key||''};
|
|
62
|
+
if(model.api_type)payload.api_type=model.api_type;
|
|
63
|
+
if(model.input_modes&&model.input_modes.length)payload.input_modes=model.input_modes;
|
|
64
|
+
const r=await api('/api/llm/test',{method:'POST',body:JSON.stringify(payload)});
|
|
65
|
+
btn.textContent='测试';btn.disabled=false;
|
|
66
|
+
// [v1.16.18] 显示详细的测试结果
|
|
67
|
+
if(r.ok){
|
|
68
|
+
if(r.tests){
|
|
69
|
+
// 有多模态测试结果
|
|
70
|
+
const parts=['文本连接: OK'+(r.text_response?' ('+r.text_response+')':'')];
|
|
71
|
+
if(r.tests.image===true)parts.push('图片识别: OK'+(r.tests.image_response?' ('+r.tests.image_response+')':''));
|
|
72
|
+
else if(r.tests.image===false)parts.push('图片识别: FAIL'+(r.tests.image_error?' - '+r.tests.image_error:''));
|
|
73
|
+
if(r.tests.video===null)parts.push('视频: 跳过(需要真实文件)');
|
|
74
|
+
if(r.tests.audio===null)parts.push('音频: 跳过(需要真实文件)');
|
|
75
|
+
showToast(parts.join('\n'),'success',8000);
|
|
76
|
+
}else{
|
|
77
|
+
showToast('连接成功: '+(r.text_response||r.response),'success');
|
|
78
|
+
}
|
|
79
|
+
}else{
|
|
80
|
+
const msg=r.error||'连接失败';
|
|
81
|
+
if(r.detail)showToast(msg+'\n'+r.detail,'danger',8000);
|
|
82
|
+
else showToast(msg,'danger');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// ── Model Library Modal ──
|
|
86
|
+
function showAddModelModal(editId){
|
|
87
|
+
const model=allModelsCache.find(m=>m.id===editId);
|
|
88
|
+
const isEdit=!!model;
|
|
89
|
+
const id=model?model.id:'';
|
|
90
|
+
const name=model?model.name:'';
|
|
91
|
+
const provider=model?model.provider:'custom';
|
|
92
|
+
const apiType=model?model.api_type:'openai-completions';
|
|
93
|
+
const baseUrl=model?model.base_url:'';
|
|
94
|
+
const apiKey=model&&model.api_key?model.api_key:'';
|
|
95
|
+
const contextWindow=model?model.context_window:128000;
|
|
96
|
+
const temperature=model?model.temperature:0.1;
|
|
97
|
+
const inputModesArr=model&&model.input_modes?model.input_modes:['text'];
|
|
98
|
+
const reasoning=model?model.reasoning:false;
|
|
99
|
+
const maxTokens=model&&model.max_tokens?model.max_tokens:4096;
|
|
100
|
+
const isFallback=model?model.is_global_fallback!==false:true;
|
|
101
|
+
const provOpts=['custom','openai','anthropic','ollama','zhipu','deepseek','moonshot','qwen','modelscope'].map(p=>`<option value="${p}" ${provider===p?'selected':''}>${p}</option>`).join('');
|
|
102
|
+
const apiTypeOpts=['openai-completions','openai-chat','anthropic','ollama'].map(t=>`<option value="${t}" ${apiType===t?'selected':''}>${t}</option>`).join('');
|
|
103
|
+
const isCustomApiType=apiType==='custom'||!['openai-completions','openai-chat','anthropic','ollama'].includes(apiType);
|
|
104
|
+
const inputModesChecked=['text','image','video','audio'].map(m=>`<label style="display:inline-flex;align-items:center;gap:4px;margin-right:12px"><input type="checkbox" class="mlInputMode" value="${m}" ${inputModesArr.indexOf(m)>-1?'checked':''}>${m}</label>`).join('');
|
|
105
|
+
$('modalContainer').innerHTML=`<div class="modal-overlay" onclick="closeModal()"><div class="modal" onclick="event.stopPropagation()">
|
|
106
|
+
<h3>${isEdit?'编辑模型':'添加模型'}</h3>
|
|
107
|
+
<div class="form-row">
|
|
108
|
+
<div class="form-group"><label>模型 ID ${isEdit?'(只读)':''}</label><input id="mlId" value="${escHtml(id)}" placeholder="如 gpt-4o" ${isEdit?'disabled style="opacity:.6"':''}></div>
|
|
109
|
+
<div class="form-group"><label>显示名称</label><input id="mlName" value="${escHtml(name)}" placeholder="如 我的 GPT-4o"></div>
|
|
110
|
+
</div>
|
|
111
|
+
<div class="form-row">
|
|
112
|
+
<div class="form-group"><label>Provider</label><select id="mlProvider">${provOpts}</select></div>
|
|
113
|
+
<div class="form-group">
|
|
114
|
+
<label>API 类型</label>
|
|
115
|
+
<select id="mlApiTypeSelect" onchange="toggleApiTypeInput('ml')">
|
|
116
|
+
${apiTypeOpts}
|
|
117
|
+
<option value="custom" ${isCustomApiType?'selected':''}>custom (自定义)</option>
|
|
118
|
+
</select>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
<div id="mlApiTypeCustomWrap" style="${isCustomApiType?'':'display:none'}" class="form-group">
|
|
122
|
+
<label>自定义 API 类型</label>
|
|
123
|
+
<input id="mlApiTypeCustom" value="${isCustomApiType?escHtml(apiType):''}" placeholder="输入自定义 API 类型">
|
|
124
|
+
</div>
|
|
125
|
+
<div class="form-group"><label>Base URL</label><input id="mlBaseUrl" value="${escHtml(baseUrl)}" placeholder="https://api.example.com/v1"></div>
|
|
126
|
+
<div class="form-group"><label>API Key</label><input id="mlApiKey" type="text" value="${escHtml(apiKey)}" placeholder="sk-..." style="width:100%"></div>
|
|
127
|
+
<div class="form-row">
|
|
128
|
+
<div class="form-group"><label>上下文窗口</label><input id="mlContextWindow" type="number" value="${contextWindow}" placeholder="128000"></div>
|
|
129
|
+
<div class="form-group"><label>最大输出</label><input id="mlMaxTokens" type="number" value="${maxTokens}" placeholder="4096"></div>
|
|
130
|
+
</div>
|
|
131
|
+
<div class="form-row">
|
|
132
|
+
<div class="form-group"><label>Temperature</label><input id="mlTemp" type="number" step="0.1" value="${temperature}" placeholder="0.1"></div>
|
|
133
|
+
<div class="form-group" style="display:flex;align-items:center">
|
|
134
|
+
<input id="mlReasoning" type="checkbox" ${reasoning?'checked':''} style="margin-right:6px"><label for="mlReasoning" style="margin:0">支持推理</label>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
<div class="form-group"><label>输入模式</label><div style="padding:8px 0">${inputModesChecked}</div></div>
|
|
138
|
+
<div class="form-group" style="display:flex;align-items:center">
|
|
139
|
+
<input id="mlGlobalFallback" type="checkbox" ${isFallback?'checked':''} style="margin-right:6px"><label for="mlGlobalFallback" style="margin:0;cursor:help" title="作为保障系统运行的最终兜底模型">全局兜底</label>
|
|
140
|
+
</div>
|
|
141
|
+
<div class="flex gap-8 mt-16"><button class="btn btn-primary" onclick="doSaveModel('${escHtml(editId)}')">${isEdit?'保存':'添加'}</button><button class="btn btn-ghost" onclick="closeModal()">取消</button></div>
|
|
142
|
+
</div></div>`;
|
|
143
|
+
}
|
|
144
|
+
function toggleApiTypeInput(prefix){
|
|
145
|
+
const select=document.getElementById(prefix+'ApiTypeSelect');
|
|
146
|
+
const customWrap=document.getElementById(prefix+'ApiTypeCustomWrap');
|
|
147
|
+
if(customWrap){
|
|
148
|
+
customWrap.style.display=select.value==='custom'?'block':'none';
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function showEditModelModal(id){showAddModelModal(decodeURIComponent(id))}
|
|
152
|
+
async function doSaveModel(editId){
|
|
153
|
+
const inputModes=Array.from(document.querySelectorAll('.mlInputMode:checked')).map(c=>c.value);
|
|
154
|
+
const apiTypeSelect=$('mlApiTypeSelect').value;
|
|
155
|
+
const apiType=apiTypeSelect==='custom'?($('mlApiTypeCustom').value||'custom'):apiTypeSelect;
|
|
156
|
+
const payload={
|
|
157
|
+
name:$('mlName').value||$('mlId').value,
|
|
158
|
+
provider:$('mlProvider').value,
|
|
159
|
+
api_type:apiType,
|
|
160
|
+
model:$('mlId').value,
|
|
161
|
+
base_url:$('mlBaseUrl').value,
|
|
162
|
+
context_window:parseInt($('mlContextWindow').value)||128000,
|
|
163
|
+
max_tokens:parseInt($('mlMaxTokens').value)||4096,
|
|
164
|
+
temperature:parseFloat($('mlTemp').value)||0.1,
|
|
165
|
+
input_modes:inputModes,
|
|
166
|
+
reasoning:$('mlReasoning').checked,
|
|
167
|
+
is_global_fallback:$('mlGlobalFallback').checked
|
|
168
|
+
};
|
|
169
|
+
// api_key 始终发送,确保保存生效
|
|
170
|
+
const apiKeyEl=$('mlApiKey');
|
|
171
|
+
if(apiKeyEl)payload.api_key=apiKeyEl.value;
|
|
172
|
+
let r;
|
|
173
|
+
if(editId){
|
|
174
|
+
r=await api(`/api/models/${encodeURIComponent(editId)}`,{method:'PUT',body:JSON.stringify(payload)});
|
|
175
|
+
}else{
|
|
176
|
+
payload.id=$('mlId').value;if(!payload.id){showToast('模型ID不能为空','danger');return}
|
|
177
|
+
r=await api('/api/models',{method:'POST',body:JSON.stringify(payload)});
|
|
178
|
+
}
|
|
179
|
+
if(r.error){showToast(r.error,'danger');return}closeModal();showToast(editId?'已保存':'已添加','success');renderLLM();
|
|
180
|
+
}
|
|
181
|
+
async function deleteModel(id,name){
|
|
182
|
+
showConfirm('删除模型','确认删除模型 "'+escHtml(name)+'" 吗?',async()=>{
|
|
183
|
+
const r=await api(`/api/models/${id}`,{method:'DELETE'});if(r.error){showToast(r.error,'danger');closeModal();return}closeModal();showToast('已删除','success');renderLLM();
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (typeof window._adminRenderers === 'undefined') window._adminRenderers = {};
|
|
188
|
+
window._adminRenderers['llm'] = renderLLM;
|
|
189
|
+
} catch(e) { console.error('[admin-llm] load error:', e); }
|
|
190
|
+
})();
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// ========== Logs ==========
|
|
2
|
+
(function(){
|
|
3
|
+
try {
|
|
4
|
+
// ========== Logs ==========
|
|
5
|
+
async function renderLogs(){
|
|
6
|
+
let html=`<div class="flex gap-8 mb-16 flex-wrap">
|
|
7
|
+
<select id="logLines" style="width:auto"><option value="100">100 行</option><option value="500" selected>500 行</option><option value="1000">1000 行</option><option value="2000">2000 行</option></select>
|
|
8
|
+
<select id="logLevel" style="width:auto"><option value="">全部级别</option><option value="ERROR">ERROR</option><option value="WARNING">WARNING</option><option value="INFO">INFO</option><option value="DEBUG">DEBUG</option></select>
|
|
9
|
+
<button class="btn btn-primary" onclick="loadLogs()">刷新</button>
|
|
10
|
+
<button class="btn btn-ghost" onclick="toggleLogStream()" id="streamBtn">▶ 实时日志</button>
|
|
11
|
+
<button class="btn btn-sm btn-ghost" onclick="clearLogViewer()" title="清空显示">🗑️ 清屏</button></div>
|
|
12
|
+
<div class="log-viewer" id="logViewer" style="height:calc(100vh - 240px);font-family:monospace;font-size:12px;line-height:1.6;overflow-y:auto;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:8px 12px">加载中...</div>`;
|
|
13
|
+
$('content').innerHTML=html;loadLogs();
|
|
14
|
+
}
|
|
15
|
+
async function loadLogs(){
|
|
16
|
+
const lines=$('logLines').value;const level=$('logLevel').value;
|
|
17
|
+
const logs=await api(`/api/logs?lines=${lines}${level?'&level='+level:''}`);
|
|
18
|
+
const viewer=$('logViewer');
|
|
19
|
+
viewer.innerHTML='';
|
|
20
|
+
if(!logs||!logs.length){viewer.innerHTML='<div style="color:var(--text3)">暂无日志</div>';return;}
|
|
21
|
+
for(const l of logs){appendLogLine(viewer,l);}
|
|
22
|
+
viewer.scrollTop=viewer.scrollHeight;
|
|
23
|
+
}
|
|
24
|
+
function appendLogLine(viewer,line){
|
|
25
|
+
var div=document.createElement('div');
|
|
26
|
+
div.style.cssText='white-space:pre-wrap;word-break:break-all;padding:1px 0;border-bottom:1px solid var(--border)';
|
|
27
|
+
// 高亮 ERROR/WARNING
|
|
28
|
+
if(line.includes('ERROR')||line.includes('CRITICAL')){div.style.color='var(--danger)';div.style.fontWeight='600';}
|
|
29
|
+
else if(line.includes('WARNING')){div.style.color='#f59e0b';}
|
|
30
|
+
else if(line.includes('DEBUG')){div.style.color='var(--text3)';}
|
|
31
|
+
div.textContent=line;
|
|
32
|
+
viewer.appendChild(div);
|
|
33
|
+
}
|
|
34
|
+
function clearLogViewer(){if($('logViewer'))$('logViewer').innerHTML='';}
|
|
35
|
+
let logStreamActive=false;let _logES=null;
|
|
36
|
+
async function toggleLogStream(){
|
|
37
|
+
if(logStreamActive){
|
|
38
|
+
logStreamActive=false;
|
|
39
|
+
if(_logES){_logES.close();_logES=null;}
|
|
40
|
+
$('streamBtn').textContent='▶ 实时日志';
|
|
41
|
+
$('streamBtn').style.background='';return;
|
|
42
|
+
}
|
|
43
|
+
logStreamActive=true;$('streamBtn').textContent='⏹ 停止实时';$('streamBtn').style.background='var(--danger)22';
|
|
44
|
+
_logES=new EventSource(API+'/api/logs/stream');
|
|
45
|
+
_logES.onmessage=function(e){
|
|
46
|
+
try{
|
|
47
|
+
var lines=JSON.parse(e.data).split('\n');
|
|
48
|
+
var viewer=$('logViewer');if(!viewer)return;
|
|
49
|
+
for(const l of lines){if(l.trim())appendLogLine(viewer,l);}
|
|
50
|
+
viewer.scrollTop=viewer.scrollHeight;
|
|
51
|
+
while(viewer.children.length>3000)viewer.removeChild(viewer.firstChild);
|
|
52
|
+
}catch(ex){}
|
|
53
|
+
};
|
|
54
|
+
// [v1.18.7] 断线自动重连
|
|
55
|
+
_logES.onerror=function(){
|
|
56
|
+
if(_logES){_logES.close();_logES=null;}
|
|
57
|
+
if(logStreamActive){
|
|
58
|
+
$('streamBtn').textContent='⟳ 重连中...';
|
|
59
|
+
setTimeout(function(){
|
|
60
|
+
if(logStreamActive){toggleLogStream();}// 重新连接
|
|
61
|
+
},3000);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (typeof window._adminRenderers === 'undefined') window._adminRenderers = {};
|
|
67
|
+
window._adminRenderers['logs'] = renderLogs;
|
|
68
|
+
} catch(e) { console.error('[admin-logs] load error:', e); }
|
|
69
|
+
})();
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// ========== Memory ==========
|
|
2
|
+
(function(){
|
|
3
|
+
try {
|
|
4
|
+
// ========== Memory ==========
|
|
5
|
+
let _memCategory='global';
|
|
6
|
+
let _memAgentFilter=''; // [v1.18.9] agent筛选
|
|
7
|
+
async function renderMemory(){
|
|
8
|
+
// [v1.18.9] 加载 agent 列表供筛选
|
|
9
|
+
let agentOpts='<option value="">全部 Agent</option>';
|
|
10
|
+
try{const ags=await api('/api/agents');
|
|
11
|
+
if(ags&&ags.length){for(const a of ags){agentOpts+=`<option value="${escHtml(a.path||a.name)}" ${_memAgentFilter===(a.path||a.name)?'selected':''}>${escHtml(a.name||a.path)}</option>`;}
|
|
12
|
+
}}catch(e){}
|
|
13
|
+
let stats={},lt=[];
|
|
14
|
+
try{stats=await api('/api/memory/stats')}catch(e){stats={error:e.message}}
|
|
15
|
+
try{lt=await api('/api/memory/list?category='+encodeURIComponent(_memCategory))}catch(e){lt=[]}
|
|
16
|
+
if(stats.error){$('content').innerHTML='<div class="empty" style="color:var(--danger)">记忆系统异常: '+escHtml(stats.error)+'</div>';return}
|
|
17
|
+
// [v1.18.9] 按 agent 筛选
|
|
18
|
+
if(_memAgentFilter){lt=lt.filter(e=>(e.agent_id||e.session_id||'').includes(_memAgentFilter));}
|
|
19
|
+
const cats=[{k:'global',l:'全局记忆'},{k:'session',l:'会话记忆'}];
|
|
20
|
+
let tabHtml='<div class="flex gap-8 mb-8" style="flex-wrap:wrap">';
|
|
21
|
+
for(const c of cats){
|
|
22
|
+
const active=c.k===_memCategory?'btn-primary':'btn-ghost';
|
|
23
|
+
const count=stats[c.k+'_count']||0;
|
|
24
|
+
tabHtml+=`<button class="btn ${active}" onclick="_memCategory='${c.k}';renderMemory()">${c.l} (${count})</button>`;
|
|
25
|
+
}
|
|
26
|
+
tabHtml+='</div>';
|
|
27
|
+
let html=`<div class="grid grid-3" style="margin-bottom:16px">
|
|
28
|
+
<div class="stat"><div class="label">总计</div><div class="value">${stats.total_count||0}</div></div>
|
|
29
|
+
<div class="stat"><div class="label">全局记忆</div><div class="value">${stats.global_count||0}</div></div>
|
|
30
|
+
<div class="stat"><div class="label">会话记忆</div><div class="value">${stats.session_count||0}</div></div></div>`;
|
|
31
|
+
html+=tabHtml;
|
|
32
|
+
html+='<div class="flex gap-8 mb-16" style="flex-wrap:wrap"><select id="memAgentFilter" onchange="_memAgentFilter=this.value;renderMemory()" style="width:auto;min-width:140px">'+agentOpts+'</select><input id="memSearch" placeholder="搜索记忆..." onkeydown="if(event.key===\'Enter\')searchMemory()" style="max-width:300px"><button class="btn btn-primary" onclick="searchMemory()">搜索</button><button class="btn btn-ghost" onclick="cleanupMemory()">清理过期</button></div>';
|
|
33
|
+
if(lt&<.length){
|
|
34
|
+
const isSession=_memCategory==='session';
|
|
35
|
+
html+='<div class="mem-list">';
|
|
36
|
+
for(const e of lt){
|
|
37
|
+
const content=(e.content||e.summary||'')||(e.role==='user'?'[用户消息]':e.role==='assistant'?'[助手回复]':'[系统]');
|
|
38
|
+
let contentPreview=escHtml(content.slice(0,300));
|
|
39
|
+
if(content.length>300)contentPreview+=`<span style="color:var(--text3)">... (${content.length}字)</span>`;
|
|
40
|
+
html+=`<div class="mem-card">
|
|
41
|
+
<div class="mem-card-header">
|
|
42
|
+
<span class="mem-key">${escHtml(e.key||e.role||'-')}</span>
|
|
43
|
+
<div class="mem-meta">
|
|
44
|
+
<span class="tag">${escHtml(e.role||'')}</span>
|
|
45
|
+
${!isSession&&e.importance!=null?'<span class="tag tag-imp">'+e.importance.toFixed(2)+'</span>':''}
|
|
46
|
+
${e.session_id?'<span class="mem-session" title="'+escHtml(e.session_id)+'">'+escHtml((e.session_id||'').split('_web_')[0])+'</span>':''}
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="mem-content">${contentPreview}</div>
|
|
50
|
+
<div class="mem-actions"><button class="btn btn-sm btn-danger" onclick="deleteMemory('${e.id}')">删除</button></div>
|
|
51
|
+
</div>`;
|
|
52
|
+
}
|
|
53
|
+
html+='</div>';
|
|
54
|
+
}else{
|
|
55
|
+
html+='<div class="empty">暂无'+(_memCategory==='session'?'会话':'全局')+'记忆</div>';
|
|
56
|
+
}
|
|
57
|
+
$('content').innerHTML=html;
|
|
58
|
+
}
|
|
59
|
+
async function searchMemory(){
|
|
60
|
+
const q=$('memSearch').value;if(!q)return;
|
|
61
|
+
const r=await api('/api/memory/search?q='+encodeURIComponent(q));
|
|
62
|
+
let html='<h3>搜索结果: '+(r.length||0)+' 条</h3>';
|
|
63
|
+
if(r&&r.length){
|
|
64
|
+
html+='<div class="mem-list">';
|
|
65
|
+
for(const e of r){
|
|
66
|
+
const content=(e.content||'').slice(0,300);
|
|
67
|
+
html+=`<div class="mem-card">
|
|
68
|
+
<div class="mem-card-header">
|
|
69
|
+
<span class="mem-key">${escHtml(e.key||'')}</span>
|
|
70
|
+
<div class="mem-meta">
|
|
71
|
+
<span class="tag">${e.category||''}</span>
|
|
72
|
+
<span class="tag">${escHtml(e.role||'')}</span>
|
|
73
|
+
<span class="mem-session">${escHtml((e.session_id||'').split('_web_')[0])}</span>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
<div class="mem-content">${escHtml(content)}</div>
|
|
77
|
+
</div>`;
|
|
78
|
+
}
|
|
79
|
+
html+='</div>';
|
|
80
|
+
}else{
|
|
81
|
+
html+='<div class="empty">未找到匹配的记忆</div>';
|
|
82
|
+
}
|
|
83
|
+
html+='<button class="btn btn-ghost mt-8" onclick="goBack()">返回</button>';$('content').innerHTML=html;
|
|
84
|
+
}
|
|
85
|
+
async function deleteMemory(id){await api(`/api/memory/${id}`,{method:'DELETE'});renderMemory()}
|
|
86
|
+
async function cleanupMemory(){const r=await api('/api/memory/cleanup',{method:'POST'});showToast('清理了 '+(r.cleaned||0)+' 条','success');renderMemory()}
|
|
87
|
+
|
|
88
|
+
if (typeof window._adminRenderers === 'undefined') window._adminRenderers = {};
|
|
89
|
+
window._adminRenderers['memory'] = renderMemory;
|
|
90
|
+
} catch(e) { console.error('[admin-memory] load error:', e); }
|
|
91
|
+
})();
|