myagent-ai 1.12.4 → 1.12.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.12.4",
3
+ "version": "1.12.5",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
@@ -1487,160 +1487,6 @@ input,textarea,select{font:inherit}
1487
1487
  .task-add-btn:hover{background:var(--accent2)}
1488
1488
  .task-add-btn:disabled{opacity:.4;cursor:default}
1489
1489
 
1490
- /* ── Debug Console ── */
1491
- .debug-console{
1492
- position:fixed;
1493
- bottom:80px;
1494
- right:20px;
1495
- width:600px;
1496
- max-width:calc(100vw - 40px);
1497
- background:var(--bg2);
1498
- border:1px solid var(--border);
1499
- border-radius:var(--radius);
1500
- box-shadow:var(--shadow-lg);
1501
- z-index:100;
1502
- display:flex;
1503
- flex-direction:column;
1504
- max-height:50vh;
1505
- }
1506
- .debug-console.hidden{display:none}
1507
- .debug-console-header{
1508
- display:flex;
1509
- align-items:center;
1510
- gap:8px;
1511
- padding:10px 14px;
1512
- background:var(--bg3);
1513
- border-bottom:1px solid var(--border);
1514
- border-radius:var(--radius) var(--radius) 0 0;
1515
- cursor:pointer;
1516
- user-select:none;
1517
- }
1518
- .debug-console-header:hover{background:var(--bg4)}
1519
- .debug-console-header .dc-icon{font-size:14px}
1520
- .debug-console-header .dc-title{flex:1;font-size:12px;font-weight:600;color:var(--text2);text-transform:uppercase;letter-spacing:.5px}
1521
- .debug-console-header .dc-count{
1522
- background:var(--accent);
1523
- color:#fff;
1524
- padding:2px 8px;
1525
- border-radius:10px;
1526
- font-size:11px;
1527
- font-weight:600;
1528
- }
1529
- .debug-console-header .dc-toggle{
1530
- color:var(--text3);
1531
- transition:transform .25s ease;
1532
- }
1533
- .debug-console-header .dc-toggle.expanded svg{transform:rotate(180deg)}
1534
- .debug-console-header .dc-toggle:hover{background:var(--bg4);color:var(--text)}
1535
- .debug-console-header .dc-toggle svg{width:14px;height:14px}
1536
- .debug-console-header .dc-clear-btn,
1537
- .debug-console-header .dc-export-btn{
1538
- padding:4px 10px;
1539
- border-radius:var(--radius-xs);
1540
- font-size:11px;
1541
- font-weight:500;
1542
- transition:var(--transition);
1543
- margin-left:4px;
1544
- }
1545
- .debug-console-header .dc-clear-btn{
1546
- background:var(--bg4);
1547
- color:var(--text2);
1548
- }
1549
- .debug-console-header .dc-clear-btn:hover{background:var(--danger);color:#fff}
1550
- .debug-console-header .dc-export-btn{
1551
- background:var(--accent);
1552
- color:#fff;
1553
- }
1554
- .debug-console-header .dc-export-btn:hover{background:var(--accent2)}
1555
- .debug-console-body{
1556
- max-height:0;
1557
- overflow:hidden;
1558
- transition:max-height .4s cubic-bezier(.4,0,.2,1);
1559
- flex:1;
1560
- }
1561
- .debug-console-body.expanded{max-height:calc(50vh - 45px);overflow-y:auto}
1562
- .debug-log-list{
1563
- padding:0;
1564
- font-family:'Cascadia Code','Consolas',monospace;
1565
- font-size:12px;
1566
- line-height:1.5;
1567
- }
1568
- .debug-log-item{
1569
- padding:8px 14px;
1570
- border-bottom:1px solid var(--border-light);
1571
- display:flex;
1572
- gap:10px;
1573
- align-items:flex-start;
1574
- }
1575
- .debug-log-item:hover{background:var(--bg3)}
1576
- .debug-log-item .log-time{
1577
- color:var(--text3);
1578
- font-size:11px;
1579
- flex-shrink:0;
1580
- min-width:60px;
1581
- }
1582
- .debug-log-item .log-type{
1583
- flex-shrink:0;
1584
- padding:1px 6px;
1585
- border-radius:4px;
1586
- font-size:10px;
1587
- font-weight:600;
1588
- min-width:60px;
1589
- text-align:center;
1590
- }
1591
- .debug-log-item .log-type.llm{background:#e0e7ff;color:#4338ca}
1592
- .debug-log-item .log-type.tool{background:#dcfce7;color:#166534}
1593
- .debug-log-item .log-type.parse{background:#fef3c7;color:#92400e}
1594
- .debug-log-item .log-type.error{background:#fee2e2;color:#991b1b}
1595
- .debug-log-item .log-type.info{background:#e0f2fe;color:#075985}
1596
- .debug-log-item .log-type.reasoning{background:#f3e8ff;color:#6b21a8}
1597
- .debug-log-item .log-type.context{background:#f1f5f9;color:#475569}
1598
- .debug-log-item .log-content{
1599
- flex:1;
1600
- word-break:break-all;
1601
- white-space:pre-wrap;
1602
- color:var(--text);
1603
- }
1604
- .debug-log-item .log-content .log-key{color:var(--accent);font-weight:600}
1605
- .debug-log-item .log-content .log-value{color:var(--text)}
1606
- .debug-log-item .log-content .log-error{color:var(--danger)}
1607
- .debug-log-item .log-content .log-success{color:var(--ok)}
1608
- .debug-log-item .log-content .log-warn{color:var(--warn)}
1609
- .debug-log-empty{
1610
- padding:20px;
1611
- text-align:center;
1612
- color:var(--text3);
1613
- font-size:12px;
1614
- }
1615
-
1616
- /* Toggle Debug Console Button */
1617
- .debug-toggle-btn{
1618
- position:fixed;
1619
- bottom:20px;
1620
- right:20px;
1621
- width:48px;
1622
- height:48px;
1623
- border-radius:50%;
1624
- background:var(--accent);
1625
- color:#fff;
1626
- font-size:20px;
1627
- display:flex;
1628
- align-items:center;
1629
- justify-content:center;
1630
- box-shadow:var(--shadow-lg);
1631
- z-index:99;
1632
- transition:var(--transition);
1633
- border:none;
1634
- cursor:pointer;
1635
- }
1636
- .debug-toggle-btn:hover{
1637
- background:var(--accent2);
1638
- transform:scale(1.1);
1639
- }
1640
- .debug-toggle-btn.active{
1641
- background:var(--danger);
1642
- }
1643
-
1644
1490
  /* ══════════════════════════════════════════════════════
1645
1491
  ── Group Chat ──
1646
1492
  ══════════════════════════════════════════════════════ */
@@ -4401,173 +4401,3 @@ function sendVoiceMessage() {
4401
4401
  }
4402
4402
  })();
4403
4403
 
4404
- // ══════════════════════════════════════════════════════
4405
- // ── Debug Console (调试控制台) ──
4406
- // ══════════════════════════════════════════════════════
4407
-
4408
- // 调试日志存储
4409
- var _debugLogs = [];
4410
- var _debugExpanded = false;
4411
-
4412
- // 添加调试日志
4413
- function addDebugLog(type, content, details) {
4414
- var log = {
4415
- type: type,
4416
- content: content,
4417
- details: details,
4418
- time: new Date().toLocaleTimeString('zh-CN', { hour12: false })
4419
- };
4420
- _debugLogs.push(log);
4421
- _renderDebugLog(log);
4422
- _updateDebugCount();
4423
- }
4424
-
4425
- // 渲染单条调试日志
4426
- function _renderDebugLog(log) {
4427
- var list = document.getElementById('debugLogList');
4428
- if (!list) return;
4429
-
4430
- var html = '<div class="debug-log-item" data-type="' + log.type + '">' +
4431
- '<span class="log-time">' + log.time + '</span>' +
4432
- '<span class="log-type ' + log.type + '">' + _getTypeLabel(log.type) + '</span>' +
4433
- '<span class="log-content">';
4434
-
4435
- // 根据类型格式化内容
4436
- if (log.details && typeof log.details === 'object') {
4437
- html += '<span class="log-key">' + escapeHtml(log.content) + '</span>';
4438
- if (log.details.error) {
4439
- html += '<span class="log-error">\n ❌ 错误: ' + escapeHtml(log.details.error) + '</span>';
4440
- }
4441
- if (log.details.success !== undefined) {
4442
- html += '<span class="log-success">\n ✅ 成功: ' + escapeHtml(String(log.details.success)) + '</span>';
4443
- }
4444
- if (log.details.result) {
4445
- var result = typeof log.details.result === 'object' ? JSON.stringify(log.details.result, null, 2) : log.details.result;
4446
- html += '<span class="log-value">\n 📤 结果: ' + escapeHtml(String(result).substring(0, 500)) + (result.length > 500 ? '...' : '') + '</span>';
4447
- }
4448
- if (log.details.tools) {
4449
- html += '<span class="log-value">\n 🔧 工具: ' + escapeHtml(log.details.tools) + '</span>';
4450
- }
4451
- if (log.details.parse_success !== undefined) {
4452
- html += '<span class="' + (log.details.parse_success ? 'log-success' : 'log-error') + '">\n 📋 解析: ' + (log.details.parse_success ? '成功' : '失败') + '</span>';
4453
- }
4454
- } else {
4455
- html += escapeHtml(log.content);
4456
- if (log.details) {
4457
- html += '<span class="log-value"> | ' + escapeHtml(String(log.details).substring(0, 300)) + '</span>';
4458
- }
4459
- }
4460
-
4461
- html += '</span></div>';
4462
-
4463
- // 如果是空列表,先清空提示
4464
- var empty = list.querySelector('.debug-log-empty');
4465
- if (empty) empty.remove();
4466
-
4467
- list.insertAdjacentHTML('beforeend', html);
4468
-
4469
- // 自动滚动到底部
4470
- var body = document.getElementById('debugConsoleBody');
4471
- if (body) {
4472
- body.scrollTop = body.scrollHeight;
4473
- }
4474
- }
4475
-
4476
- // 获取类型标签
4477
- function _getTypeLabel(type) {
4478
- var labels = {
4479
- 'llm': 'LLM',
4480
- 'tool': '工具',
4481
- 'parse': '解析',
4482
- 'error': '错误',
4483
- 'info': '信息',
4484
- 'reasoning': '推理',
4485
- 'context': '上下文'
4486
- };
4487
- return labels[type] || type.toUpperCase();
4488
- }
4489
-
4490
- // 更新日志计数
4491
- function _updateDebugCount() {
4492
- var countEl = document.getElementById('debugLogCount');
4493
- if (countEl) {
4494
- countEl.textContent = _debugLogs.length;
4495
- }
4496
- }
4497
-
4498
- // 切换调试控制台显示
4499
- function toggleDebugConsole(event) {
4500
- if (event) event.stopPropagation();
4501
-
4502
- var panel = document.getElementById('debugConsole');
4503
- var body = document.getElementById('debugConsoleBody');
4504
- var toggle = document.getElementById('debugToggle');
4505
- var btn = document.getElementById('debugToggleBtn');
4506
-
4507
- if (!panel) return;
4508
-
4509
- _debugExpanded = !_debugExpanded;
4510
-
4511
- if (_debugExpanded) {
4512
- panel.classList.remove('hidden');
4513
- body.classList.add('expanded');
4514
- toggle.classList.add('expanded');
4515
- btn.classList.add('active');
4516
- } else {
4517
- body.classList.remove('expanded');
4518
- toggle.classList.remove('expanded');
4519
- btn.classList.remove('active');
4520
- }
4521
- }
4522
-
4523
- // 清空调试日志
4524
- function clearDebugConsole(event) {
4525
- if (event) event.stopPropagation();
4526
- _debugLogs = [];
4527
- var list = document.getElementById('debugLogList');
4528
- if (list) {
4529
- list.innerHTML = '<div class="debug-log-empty">暂无调试日志<br>发送消息后会记录执行过程</div>';
4530
- }
4531
- _updateDebugCount();
4532
- }
4533
-
4534
- // 导出调试日志
4535
- function exportDebugLog(event) {
4536
- if (event) event.stopPropagation();
4537
-
4538
- if (_debugLogs.length === 0) {
4539
- toast('暂无调试日志可导出', 'info');
4540
- return;
4541
- }
4542
-
4543
- var content = 'MyAgent 调试日志\n';
4544
- content += '导出时间: ' + new Date().toLocaleString('zh-CN') + '\n';
4545
- content += '总计 ' + _debugLogs.length + ' 条日志\n';
4546
- content += '='.repeat(60) + '\n\n';
4547
-
4548
- for (var i = 0; i < _debugLogs.length; i++) {
4549
- var log = _debugLogs[i];
4550
- content += '[' + log.time + '] [' + _getTypeLabel(log.type) + '] ' + log.content + '\n';
4551
- if (log.details) {
4552
- if (log.details.error) content += ' ❌ 错误: ' + log.details.error + '\n';
4553
- if (log.details.success !== undefined) content += ' ✅ 成功: ' + log.details.success + '\n';
4554
- if (log.details.result) content += ' 📤 结果: ' + (typeof log.details.result === 'object' ? JSON.stringify(log.details.result) : log.details.result) + '\n';
4555
- if (log.details.tools) content += ' 🔧 工具: ' + log.details.tools + '\n';
4556
- }
4557
- content += '\n';
4558
- }
4559
-
4560
- var blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
4561
- var url = URL.createObjectURL(blob);
4562
- var a = document.createElement('a');
4563
- a.href = url;
4564
- a.download = 'myagent-debug-' + new Date().toISOString().slice(0, 10) + '.log';
4565
- a.click();
4566
- URL.revokeObjectURL(url);
4567
-
4568
- toast('调试日志已导出', 'success');
4569
- }
4570
-
4571
- // 公开函数供外部调用
4572
- window.addDebugLog = addDebugLog;
4573
-
@@ -1394,12 +1394,6 @@ async function sendMessage() {
1394
1394
  } else if (evt.type === 'v2_context') {
1395
1395
  // V2 context event — confirms we're in V2 mode
1396
1396
  _isV2Mode = true;
1397
- // 记录到调试控制台
1398
- if (window.addDebugLog) {
1399
- window.addDebugLog('context', '收到上下文事件', {
1400
- context: evt.context ? evt.context.substring(0, 200) : ''
1401
- });
1402
- }
1403
1397
  } else if (evt.type === 'v2_output_parsed') {
1404
1398
  // LLM output was parsed into structured format
1405
1399
  // IMPORTANT: Also mark V2 mode here — some server configs may skip v2_context
@@ -1416,35 +1410,11 @@ async function sendMessage() {
1416
1410
  _finishReceived = true;
1417
1411
  state.taskItems = [];
1418
1412
  if (typeof renderTaskList === 'function') renderTaskList();
1419
- if (window.addDebugLog) {
1420
- window.addDebugLog('task', 'finish 标签收到,已清空任务列表', {});
1421
- }
1422
- }
1423
- // 记录到调试控制台
1424
- if (window.addDebugLog) {
1425
- var toolsStr = evt.data && evt.data.tools_count ? '需调用 ' + evt.data.tools_count + ' 个工具' : '无工具调用';
1426
- window.addDebugLog('parse', 'XML 解析成功', {
1427
- parse_success: evt.data && evt.data.parse_success,
1428
- usersays: evt.data && evt.data.usersays_correct,
1429
- task_plan: evt.data && evt.data.task_plan,
1430
- tools: toolsStr,
1431
- finish: evt.data && evt.data.finish
1432
- });
1433
1413
  }
1434
1414
  throttledStreamUpdate(msgIdx);
1435
1415
  } else if (evt.type === 'v2_tool_start') {
1436
1416
  // A tool is about to be executed
1437
1417
  // evt.tool contains: {beforecalltext, toolname, parms, timeout, callback}
1438
- // 记录到调试控制台
1439
- if (window.addDebugLog) {
1440
- window.addDebugLog('tool', '开始调用工具: ' + (evt.tool.toolname || '未知'), {
1441
- tool: evt.tool.toolname,
1442
- beforecalltext: evt.tool.beforecalltext,
1443
- params: evt.tool.parms,
1444
- timeout: evt.tool.timeout,
1445
- callback: evt.tool.callback
1446
- });
1447
- }
1448
1418
  // Flush reasoning text BEFORE tool call to create speak→tool pattern
1449
1419
  flushV2Reasoning();
1450
1420
  flushCurrentText();
@@ -1477,18 +1447,6 @@ async function sendMessage() {
1477
1447
  }
1478
1448
  // evt.tool contains: {beforecalltext, toolname, ...}
1479
1449
  // evt.result contains: {success, output, error, ...}
1480
- // 记录到调试控制台
1481
- if (window.addDebugLog) {
1482
- var _toolResult = evt.result || {};
1483
- var _toolInfo = evt.tool || {};
1484
- window.addDebugLog(_toolResult.success ? 'tool' : 'error',
1485
- (_toolInfo.toolname || '工具') + ' 执行' + (_toolResult.success ? '成功' : '失败'), {
1486
- tool: _toolInfo.toolname,
1487
- success: _toolResult.success,
1488
- error: _toolResult.error,
1489
- result: _toolResult.output || _toolResult.error
1490
- });
1491
- }
1492
1450
  var _r = evt.result || {};
1493
1451
  var _t = evt.tool || {};
1494
1452
  var resultEvent = {
@@ -1517,10 +1475,6 @@ async function sendMessage() {
1517
1475
  }
1518
1476
  } else if (evt.type === 'v2_ask_user') {
1519
1477
  // Agent wants to ask the user a question
1520
- // 记录到调试控制台
1521
- if (window.addDebugLog) {
1522
- window.addDebugLog('reasoning', '询问用户', { question: evt.question });
1523
- }
1524
1478
  flushCurrentText();
1525
1479
  var askEvent = {
1526
1480
  type: 'v2_ask',
@@ -1540,10 +1494,6 @@ async function sendMessage() {
1540
1494
  (evt.content ? evt.content.substring(0, 100) : '');
1541
1495
  } else if (evt.type === 'v2_reasoning') {
1542
1496
  // V2 reasoning text from model — this IS the user-facing content in V2 mode
1543
- // 记录到调试控制台
1544
- if (window.addDebugLog && evt.content) {
1545
- window.addDebugLog('reasoning', '模型回复', { content: evt.content.substring(0, 200) });
1546
- }
1547
1497
  if (!state.messages[msgIdx]._v2Reasoning) {
1548
1498
  state.messages[msgIdx]._v2Reasoning = '';
1549
1499
  }
@@ -1572,13 +1522,6 @@ async function sendMessage() {
1572
1522
  // Flush remaining V2 reasoning as final text part
1573
1523
  flushV2Reasoning();
1574
1524
  flushCurrentText();
1575
- // 记录到调试控制台
1576
- if (window.addDebugLog) {
1577
- window.addDebugLog('info', '会话完成', {
1578
- exec_events_count: evt.exec_events ? evt.exec_events.length : 0,
1579
- error: evt.error
1580
- });
1581
- }
1582
1525
  // done 事件提供最终事件列表(可能有去重/合并)
1583
1526
  if (evt.exec_events && evt.exec_events.length > 0) {
1584
1527
  allExecEvents = evt.exec_events;
@@ -1598,10 +1541,6 @@ async function sendMessage() {
1598
1541
  state.messages[msgIdx].reasoning = evt.content;
1599
1542
  throttledStreamUpdate(msgIdx);
1600
1543
  } else if (evt.type === 'error') {
1601
- // 记录到调试控制台
1602
- if (window.addDebugLog) {
1603
- window.addDebugLog('error', '收到错误事件', { error: evt.error });
1604
- }
1605
1544
  flushCurrentText();
1606
1545
  currentText = '❌ ' + evt.error;
1607
1546
  msgParts.push({type: 'text', content: currentText});
@@ -72,21 +72,6 @@
72
72
  </div>
73
73
  </div>
74
74
 
75
- <!-- Debug Console Panel -->
76
- <div class="debug-console hidden" id="debugConsole">
77
- <div class="debug-console-header" onclick="toggleDebugConsole()">
78
- <span class="dc-icon">🖥️</span>
79
- <span class="dc-title">调试控制台</span>
80
- <span class="dc-count" id="debugLogCount">0</span>
81
- <span class="dc-toggle" id="debugToggle"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg></span>
82
- <button class="dc-clear-btn" onclick="clearDebugConsole(event)">清空</button>
83
- <button class="dc-export-btn" onclick="exportDebugLog(event)">导出</button>
84
- </div>
85
- <div class="debug-console-body" id="debugConsoleBody">
86
- <div class="debug-log-list" id="debugLogList"></div>
87
- </div>
88
- </div>
89
-
90
75
  <!-- Input -->
91
76
  <div class="input-area">
92
77
  <div class="input-wrapper">
@@ -173,10 +158,5 @@
173
158
  </div>
174
159
  </div>
175
160
  </div>
176
-
177
- <!-- Debug Console Toggle Button (Floating) -->
178
- <button class="debug-toggle-btn" id="debugToggleBtn" onclick="toggleDebugConsole()" title="调试控制台">
179
- 🖥️
180
- </button>
181
161
  </div>
182
162
 
package/web/ui/index.html CHANGED
@@ -224,7 +224,8 @@ tr:hover{background:var(--surface2)}
224
224
  </div>
225
225
  <div class="mobile-overlay" id="adminMobileOverlay" onclick="closeMobileSidebar()"></div>
226
226
  <div class="main">
227
- <div class="header"><div style="display:flex;align-items:center;gap:12px"><button class="header-btn" id="mobileMenuBtn" onclick="toggleMobileSidebar()" style="display:none" title="菜单"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="20" height="20"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg></button><button class="header-btn" onclick="window.location.href='/ui/chat/chat_container.html'" title="返回聊天" style="display:flex;align-items:center;gap:4px;font-size:13px;white-space:nowrap"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><polyline points="15 18 9 12 15 6"/></svg>返回聊天</button><h2 id="pageTitle">📊 仪表盘</h2><button class="header-btn" id="themeToggle" title="切换主题"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="18" height="18"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg></button></div><div style="display:flex;align-items:center;gap:8px"><span class="status-dot"></span>运行中</div></div>
227
+ <div class="header"><div style="display:flex;align-items:center;gap:12px"><button class="header-btn" id="mobileMenuBtn" onclick="toggleMobileSidebar()" style="display:none" title="菜单"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="20" height="20"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg></button><h2 id="pageTitle">📊 仪表盘</h2><button class="header-btn" id="themeToggle" title="切换主题"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="18" height="18"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg></button></div><div style="display:flex;align-items:center;gap:8px"><span class="status-dot"></span>运行中</div></div>
228
+ <div id="backToChatBar" style="padding:8px 24px;background:var(--surface);border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px"><a href="/ui/chat/chat_container.html" style="display:inline-flex;align-items:center;gap:4px;font-size:13px;color:var(--primary);text-decoration:none;white-space:nowrap;padding:4px 10px;border-radius:6px;border:1px solid var(--border);transition:all .15s" onmouseover="this.style.background='var(--surface2)'" onmouseout="this.style.background='transparent'"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><polyline points="15 18 9 12 15 6"/></svg>返回聊天</a><span style="font-size:12px;color:var(--text3)">← 管理后台</span></div>
228
229
  <div class="content" id="content"></div>
229
230
  </div>
230
231
  <div id="modalContainer"></div>
@@ -1158,8 +1159,8 @@ async function renderLLM(){
1158
1159
  <td>${m.reasoning?'<span class="badge badge-purple">是</span>':'-'}</td>
1159
1160
  <td><span style="cursor:help" title="作为保障系统运行的最终兜底模型">${isFallback?'<span class="badge badge-green">&#9745;</span>':'<span class="badge badge-red">&#9744;</span>'}</span></td>
1160
1161
  <td>${m.enabled?'<span class="badge badge-green">启用</span>':'<span class="badge badge-red">禁用</span>'}</td>
1161
- <td><button class="btn btn-sm btn-ghost" onclick="showEditModelModal('${escHtml(m.id)}')">编辑</button>
1162
- <button class="btn btn-sm btn-success" onclick="testModel('${escHtml(m.id)}')">测试</button>
1162
+ <td><button class="btn btn-sm btn-ghost" onclick="showEditModelModal(encodeURIComponent('${escHtml(m.id)}'))">编辑</button>
1163
+ <button class="btn btn-sm btn-success" onclick="testModel(encodeURIComponent('${escHtml(m.id)}'))">测试</button>
1163
1164
  <button class="btn btn-sm btn-danger" onclick="deleteModel('${escHtml(m.id)}','${escHtml(String(m.name||'').replace(/'/g,"\\'"))}')">删除</button></td></tr>`;
1164
1165
  }
1165
1166
  html+='</table>';
@@ -1169,6 +1170,7 @@ async function renderLLM(){
1169
1170
  }
1170
1171
  // saveLLM / testLLM / showTestCode / hideTestCode 已移除(全局模型配置卡片已删除)
1171
1172
  async function testModel(modelId){
1173
+ modelId=decodeURIComponent(modelId);
1172
1174
  const model=allModelsCache.find(m=>m.id===modelId);
1173
1175
  if(!model){showToast('模型不存在','danger');return;}
1174
1176
  const btn=event.target;btn.textContent='测试中...';btn.disabled=true;
@@ -1245,7 +1247,7 @@ function toggleApiTypeInput(prefix){
1245
1247
  customWrap.style.display=select.value==='custom'?'block':'none';
1246
1248
  }
1247
1249
  }
1248
- function showEditModelModal(id){showAddModelModal(id)}
1250
+ function showEditModelModal(id){showAddModelModal(decodeURIComponent(id))}
1249
1251
  async function doSaveModel(editId){
1250
1252
  const inputModes=Array.from(document.querySelectorAll('.mlInputMode:checked')).map(c=>c.value);
1251
1253
  const apiTypeSelect=$('mlApiTypeSelect').value;
@@ -1263,10 +1265,12 @@ async function doSaveModel(editId){
1263
1265
  reasoning:$('mlReasoning').checked,
1264
1266
  is_global_fallback:$('mlGlobalFallback').checked
1265
1267
  };
1266
- if($('mlApiKey').value!==undefined)payload.api_key=$('mlApiKey').value;
1268
+ // api_key 始终发送,确保保存生效
1269
+ const apiKeyEl=$('mlApiKey');
1270
+ if(apiKeyEl)payload.api_key=apiKeyEl.value;
1267
1271
  let r;
1268
1272
  if(editId){
1269
- r=await api(`/api/models/${editId}`,{method:'PUT',body:JSON.stringify(payload)});
1273
+ r=await api(`/api/models/${encodeURIComponent(editId)}`,{method:'PUT',body:JSON.stringify(payload)});
1270
1274
  }else{
1271
1275
  payload.id=$('mlId').value;if(!payload.id){showToast('模型ID不能为空','danger');return}
1272
1276
  r=await api('/api/models',{method:'POST',body:JSON.stringify(payload)});