ai-browser 0.2.4 → 0.2.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.
Files changed (62) hide show
  1. package/README.md +30 -0
  2. package/dist/agent/agent-loop.d.ts +8 -2
  3. package/dist/agent/agent-loop.d.ts.map +1 -1
  4. package/dist/agent/agent-loop.js +138 -86
  5. package/dist/agent/agent-loop.js.map +1 -1
  6. package/dist/agent/config.d.ts +5 -0
  7. package/dist/agent/config.d.ts.map +1 -1
  8. package/dist/agent/config.js +5 -0
  9. package/dist/agent/config.js.map +1 -1
  10. package/dist/agent/content-budget.d.ts +11 -0
  11. package/dist/agent/content-budget.d.ts.map +1 -0
  12. package/dist/agent/content-budget.js +129 -0
  13. package/dist/agent/content-budget.js.map +1 -0
  14. package/dist/agent/conversation-manager.d.ts +48 -0
  15. package/dist/agent/conversation-manager.d.ts.map +1 -0
  16. package/dist/agent/conversation-manager.js +157 -0
  17. package/dist/agent/conversation-manager.js.map +1 -0
  18. package/dist/agent/error-recovery.d.ts +29 -0
  19. package/dist/agent/error-recovery.d.ts.map +1 -0
  20. package/dist/agent/error-recovery.js +72 -0
  21. package/dist/agent/error-recovery.js.map +1 -0
  22. package/dist/agent/page-state-cache.d.ts +22 -0
  23. package/dist/agent/page-state-cache.d.ts.map +1 -0
  24. package/dist/agent/page-state-cache.js +71 -0
  25. package/dist/agent/page-state-cache.js.map +1 -0
  26. package/dist/agent/progress-estimator.d.ts +17 -0
  27. package/dist/agent/progress-estimator.d.ts.map +1 -0
  28. package/dist/agent/progress-estimator.js +67 -0
  29. package/dist/agent/progress-estimator.js.map +1 -0
  30. package/dist/agent/prompt.d.ts +1 -1
  31. package/dist/agent/prompt.d.ts.map +1 -1
  32. package/dist/agent/prompt.js +83 -48
  33. package/dist/agent/prompt.js.map +1 -1
  34. package/dist/agent/token-tracker.d.ts +22 -0
  35. package/dist/agent/token-tracker.d.ts.map +1 -0
  36. package/dist/agent/token-tracker.js +29 -0
  37. package/dist/agent/token-tracker.js.map +1 -0
  38. package/dist/agent/tool-usage-tracker.d.ts +45 -0
  39. package/dist/agent/tool-usage-tracker.d.ts.map +1 -0
  40. package/dist/agent/tool-usage-tracker.js +145 -0
  41. package/dist/agent/tool-usage-tracker.js.map +1 -0
  42. package/dist/agent/types.d.ts +24 -0
  43. package/dist/agent/types.d.ts.map +1 -1
  44. package/dist/api/routes.d.ts.map +1 -1
  45. package/dist/api/routes.js +75 -1
  46. package/dist/api/routes.js.map +1 -1
  47. package/dist/mcp/ai-markdown.d.ts +2 -0
  48. package/dist/mcp/ai-markdown.d.ts.map +1 -0
  49. package/dist/mcp/ai-markdown.js +1739 -0
  50. package/dist/mcp/ai-markdown.js.map +1 -0
  51. package/dist/mcp/browser-mcp-server.d.ts.map +1 -1
  52. package/dist/mcp/browser-mcp-server.js +279 -49
  53. package/dist/mcp/browser-mcp-server.js.map +1 -1
  54. package/dist/mcp/task-tools.d.ts.map +1 -1
  55. package/dist/mcp/task-tools.js +107 -13
  56. package/dist/mcp/task-tools.js.map +1 -1
  57. package/dist/task/tool-actions.d.ts +4 -0
  58. package/dist/task/tool-actions.d.ts.map +1 -1
  59. package/dist/task/tool-actions.js +72 -0
  60. package/dist/task/tool-actions.js.map +1 -1
  61. package/package.json +3 -2
  62. package/public/index.html +593 -41
package/public/index.html CHANGED
@@ -565,7 +565,27 @@ body {
565
565
  justify-content: center;
566
566
  background: #010409;
567
567
  padding: 12px;
568
+ position: relative;
569
+ }
570
+ .screenshot-loading::before {
571
+ content: '';
572
+ position: absolute;
573
+ inset: 0;
574
+ background: rgba(0,0,0,0.4);
575
+ z-index: 1;
576
+ }
577
+ .screenshot-loading::after {
578
+ content: '';
579
+ position: absolute;
580
+ width: 24px; height: 24px;
581
+ border: 2px solid var(--border);
582
+ border-top-color: var(--accent);
583
+ border-radius: 50%;
584
+ animation: spin 0.8s linear infinite;
585
+ z-index: 2;
568
586
  }
587
+ @keyframes spin { to { transform: rotate(360deg); } }
588
+ .screenshot-updated { box-shadow: inset 0 0 0 2px var(--accent); transition: box-shadow 0.3s ease; }
569
589
  .preview-body img {
570
590
  max-width: 100%;
571
591
  max-height: 100%;
@@ -660,6 +680,26 @@ body {
660
680
  color: var(--accent);
661
681
  }
662
682
  .msg-tool-call .label { color: var(--accent); font-weight: 600; }
683
+ .msg-tool-call .tool-desc { color: var(--text-primary); font-size: 13px; }
684
+ .msg-tool-call details.tool-raw {
685
+ margin-top: 4px;
686
+ border: 1px solid var(--border);
687
+ border-radius: var(--radius-sm);
688
+ overflow: hidden;
689
+ }
690
+ .msg-tool-call details.tool-raw summary {
691
+ padding: 3px 8px;
692
+ font-size: 11px;
693
+ color: var(--text-muted);
694
+ cursor: pointer;
695
+ background: var(--bg-surface);
696
+ }
697
+ .msg-tool-call details.tool-raw summary:hover { color: var(--text-secondary); }
698
+ .msg-tool-call details.tool-raw pre {
699
+ margin: 0;
700
+ padding: 6px 8px;
701
+ font-size: 11px;
702
+ }
663
703
  .msg-tool-call pre {
664
704
  margin: 4px 0 0;
665
705
  color: var(--text-primary);
@@ -746,6 +786,35 @@ body {
746
786
  font-style: normal;
747
787
  }
748
788
 
789
+ /* Progress bar */
790
+ .progress-bar-wrap {
791
+ height: 3px;
792
+ background: var(--bg-overlay);
793
+ border-radius: 2px;
794
+ overflow: hidden;
795
+ margin: 0;
796
+ display: none;
797
+ }
798
+ .progress-bar-wrap.active { display: block; }
799
+ .progress-bar-fill {
800
+ height: 100%;
801
+ background: var(--accent);
802
+ border-radius: 2px;
803
+ transition: width 0.4s ease;
804
+ width: 0%;
805
+ }
806
+ .progress-info {
807
+ display: none;
808
+ font-size: 11px;
809
+ color: var(--text-muted);
810
+ padding: 4px 16px;
811
+ gap: 12px;
812
+ background: var(--bg-surface);
813
+ }
814
+ .progress-info.active { display: flex; align-items: center; }
815
+ .progress-phase { text-transform: capitalize; color: var(--text-secondary); }
816
+ .progress-percent { color: var(--accent); font-weight: 600; }
817
+
749
818
  /* Typing indicator */
750
819
  .typing-indicator {
751
820
  display: none;
@@ -812,6 +881,141 @@ body {
812
881
  justify-content: flex-end;
813
882
  }
814
883
 
884
+ /* ===== Tasks View Styles ===== */
885
+ .tasks-toolbar {
886
+ background: var(--bg-surface);
887
+ border-bottom: 1px solid var(--border);
888
+ padding: 10px 20px;
889
+ display: flex;
890
+ gap: 8px;
891
+ align-items: center;
892
+ flex-shrink: 0;
893
+ }
894
+ .tasks-list {
895
+ flex: 1;
896
+ overflow-y: auto;
897
+ padding: 16px;
898
+ display: grid;
899
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
900
+ gap: 12px;
901
+ align-content: start;
902
+ }
903
+ .task-card {
904
+ background: var(--bg-surface);
905
+ border: 1px solid var(--border);
906
+ border-radius: var(--radius-lg);
907
+ padding: 14px;
908
+ cursor: pointer;
909
+ transition: all var(--transition);
910
+ }
911
+ .task-card:hover { border-color: var(--accent); }
912
+ .task-card .task-header {
913
+ display: flex;
914
+ justify-content: space-between;
915
+ align-items: center;
916
+ margin-bottom: 8px;
917
+ }
918
+ .task-card .task-id {
919
+ font-size: 11px;
920
+ color: var(--text-muted);
921
+ font-family: monospace;
922
+ }
923
+ .task-status {
924
+ display: inline-block;
925
+ padding: 2px 8px;
926
+ border-radius: var(--radius-sm);
927
+ font-size: 11px;
928
+ font-weight: 600;
929
+ }
930
+ .task-status.running { background: rgba(88,166,255,0.15); color: var(--accent); }
931
+ .task-status.done { background: rgba(35,134,54,0.15); color: #3fb950; }
932
+ .task-status.failed { background: rgba(218,54,51,0.15); color: #f85149; }
933
+ .task-status.pending { background: var(--bg-overlay); color: var(--text-muted); }
934
+ .task-card .task-goal {
935
+ font-size: 14px;
936
+ color: var(--text-primary);
937
+ margin-bottom: 6px;
938
+ overflow: hidden;
939
+ text-overflow: ellipsis;
940
+ white-space: nowrap;
941
+ }
942
+ .task-card .task-meta {
943
+ font-size: 12px;
944
+ color: var(--text-muted);
945
+ }
946
+ .task-detail {
947
+ flex: 1;
948
+ overflow-y: auto;
949
+ padding: 16px;
950
+ }
951
+ .task-detail-header {
952
+ display: flex;
953
+ align-items: center;
954
+ gap: 12px;
955
+ margin-bottom: 16px;
956
+ }
957
+ .task-detail-back {
958
+ background: none;
959
+ border: none;
960
+ color: var(--accent);
961
+ cursor: pointer;
962
+ font-size: 14px;
963
+ padding: 4px 8px;
964
+ }
965
+ .task-detail-back:hover { text-decoration: underline; }
966
+ .task-detail .meta-grid {
967
+ display: grid;
968
+ grid-template-columns: 120px 1fr;
969
+ row-gap: 8px;
970
+ font-size: 14px;
971
+ margin-bottom: 16px;
972
+ }
973
+ .task-detail .meta-key { color: var(--text-muted); }
974
+ .task-detail pre {
975
+ background: var(--bg-canvas);
976
+ border: 1px solid var(--border);
977
+ border-radius: var(--radius-md);
978
+ padding: 12px;
979
+ overflow: auto;
980
+ max-height: 400px;
981
+ font-size: 13px;
982
+ white-space: pre-wrap;
983
+ word-break: break-all;
984
+ }
985
+ .task-create-form {
986
+ max-width: 600px;
987
+ }
988
+ .task-create-form label {
989
+ display: block;
990
+ font-size: 13px;
991
+ color: var(--text-secondary);
992
+ margin: 12px 0 4px;
993
+ }
994
+ .task-create-form input,
995
+ .task-create-form textarea {
996
+ width: 100%;
997
+ padding: 8px 12px;
998
+ font-size: 14px;
999
+ border: 1px solid var(--border);
1000
+ border-radius: var(--radius-md);
1001
+ background: var(--bg-canvas);
1002
+ color: var(--text-primary);
1003
+ outline: none;
1004
+ }
1005
+ .task-create-form input:focus,
1006
+ .task-create-form textarea:focus { border-color: var(--accent); }
1007
+ .task-create-form textarea { min-height: 80px; resize: vertical; }
1008
+ .task-create-form .form-row {
1009
+ display: grid;
1010
+ grid-template-columns: 1fr 1fr;
1011
+ gap: 12px;
1012
+ }
1013
+ .task-create-form .form-actions {
1014
+ margin-top: 16px;
1015
+ display: flex;
1016
+ gap: 8px;
1017
+ }
1018
+
815
1019
  /* ===== Responsive ===== */
816
1020
  @media (max-width: 900px) {
817
1021
  .sem-body { flex-direction: column; padding-bottom: 28px; }
@@ -820,6 +1024,23 @@ body {
820
1024
  #view-agent .agent-main { flex-direction: column; }
821
1025
  .chat-panel, .preview-panel { width: 100%; border-right: none; }
822
1026
  .preview-panel { border-top: 1px solid var(--border); }
1027
+ .tasks-list { grid-template-columns: 1fr; }
1028
+ }
1029
+ @media (max-width: 768px) {
1030
+ .app-logo { font-size: 13px; }
1031
+ .nav-tab { padding: 5px 10px; font-size: 12px; }
1032
+ .preview-panel { max-height: 40vh; }
1033
+ .progress-info { padding: 3px 12px; font-size: 10px; }
1034
+ .modal { width: 95vw; padding: 16px; }
1035
+ .task-create-form .form-row { grid-template-columns: 1fr; }
1036
+ }
1037
+ @media (max-width: 480px) {
1038
+ .preview-panel { display: none; }
1039
+ .chat-panel { width: 100%; border-right: none; }
1040
+ .chat-input-bar input { font-size: 16px; padding: 12px 14px; }
1041
+ .msg-tool-result .result-content { max-height: 80px; }
1042
+ .header-right { gap: 4px; }
1043
+ .btn { padding: 5px 8px; font-size: 12px; }
823
1044
  }
824
1045
  </style>
825
1046
  </head>
@@ -830,6 +1051,7 @@ body {
830
1051
  <div class="nav-tabs">
831
1052
  <button class="nav-tab active" onclick="switchView('semantic')">Semantic</button>
832
1053
  <button class="nav-tab" onclick="switchView('agent')">Agent</button>
1054
+ <button class="nav-tab" onclick="switchView('tasks')">Tasks</button>
833
1055
  </div>
834
1056
  <div class="header-right" id="headerRight">
835
1057
  <button class="btn" id="btnCloseSession" style="display:none" onclick="Sem.closeSession()">Close Session</button>
@@ -912,6 +1134,14 @@ body {
912
1134
  <div class="typing-dot"></div>
913
1135
  <div class="typing-dot"></div>
914
1136
  </div>
1137
+ <div class="progress-bar-wrap" id="progressBar">
1138
+ <div class="progress-bar-fill" id="progressFill"></div>
1139
+ </div>
1140
+ <div class="progress-info" id="progressInfo">
1141
+ <span class="progress-phase" id="progressPhase"></span>
1142
+ <span class="progress-percent" id="progressPercent"></span>
1143
+ <span id="progressSteps"></span>
1144
+ </div>
915
1145
  <div class="chat-input-bar">
916
1146
  <input type="text" id="taskInput" placeholder="Enter a task, e.g.: Open Baidu and search for weather">
917
1147
  <button class="btn-send" id="sendBtn" onclick="Agent.start()">Send</button>
@@ -934,9 +1164,20 @@ body {
934
1164
  </div>
935
1165
  </div>
936
1166
  </div>
937
- </main>
938
1167
 
939
- <!-- ===== Settings Modal (shared) ===== -->
1168
+ <!-- Tasks View -->
1169
+ <div id="view-tasks" class="view">
1170
+ <div class="tasks-toolbar">
1171
+ <button class="btn btn-primary" id="btnNewTask" onclick="Tasks.showCreate()">+ New Task</button>
1172
+ <button class="btn" onclick="Tasks.refreshList()">Refresh</button>
1173
+ </div>
1174
+ <div id="tasksList" class="tasks-list">
1175
+ <div class="placeholder-text">Loading tasks...</div>
1176
+ </div>
1177
+ <div id="taskDetail" class="task-detail" style="display:none"></div>
1178
+ <div id="taskCreate" class="task-detail" style="display:none"></div>
1179
+ </div>
1180
+ </main>
940
1181
  <div class="modal-overlay hidden" id="settingsModal">
941
1182
  <div class="modal">
942
1183
  <h2>Model Settings</h2>
@@ -955,8 +1196,10 @@ body {
955
1196
  </label>
956
1197
  <div class="modal-btns">
957
1198
  <button class="btn" onclick="closeSettings()">Cancel</button>
1199
+ <button class="btn" id="btnTestConn" onclick="testConnection()">Test Connection</button>
958
1200
  <button class="btn btn-save" onclick="saveSettings()">Save</button>
959
1201
  </div>
1202
+ <div id="connTestResult" style="margin-top:8px;font-size:12px;display:none;"></div>
960
1203
  </div>
961
1204
  </div>
962
1205
 
@@ -1024,16 +1267,60 @@ function saveSettings() {
1024
1267
  closeSettings();
1025
1268
  }
1026
1269
 
1270
+ function testConnection() {
1271
+ var btn = document.getElementById('btnTestConn');
1272
+ var result = document.getElementById('connTestResult');
1273
+ btn.disabled = true;
1274
+ btn.textContent = 'Testing...';
1275
+ result.style.display = 'block';
1276
+ result.style.color = 'var(--text-muted)';
1277
+ result.textContent = 'Connecting...';
1278
+
1279
+ var body = {
1280
+ baseURL: document.getElementById('cfgBaseURL').value.trim(),
1281
+ model: document.getElementById('cfgModel').value.trim(),
1282
+ apiKey: document.getElementById('cfgApiKey').value.trim(),
1283
+ };
1284
+
1285
+ fetch('/v1/llm/test', {
1286
+ method: 'POST',
1287
+ headers: { 'Content-Type': 'application/json' },
1288
+ body: JSON.stringify(body),
1289
+ }).then(function(r) { return r.json(); })
1290
+ .then(function(data) {
1291
+ if (data.ok) {
1292
+ result.style.color = '#3fb950';
1293
+ result.textContent = '\u2713 Connected — ' + (data.model || 'OK') + ' (' + (data.latencyMs || '?') + 'ms)';
1294
+ } else {
1295
+ result.style.color = '#f85149';
1296
+ result.textContent = '\u2717 Failed: ' + (data.error || 'Unknown error');
1297
+ }
1298
+ }).catch(function(err) {
1299
+ result.style.color = '#f85149';
1300
+ result.textContent = '\u2717 ' + err.message;
1301
+ }).finally(function() {
1302
+ btn.disabled = false;
1303
+ btn.textContent = 'Test Connection';
1304
+ });
1305
+ }
1306
+
1027
1307
  // ========== Navigation ==========
1028
1308
  function switchView(view) {
1029
1309
  App.currentView = view;
1030
1310
  location.hash = view;
1311
+ // Clean up Tasks polling when leaving
1312
+ if (view !== 'tasks' && Tasks.pollTimer) {
1313
+ clearInterval(Tasks.pollTimer);
1314
+ Tasks.pollTimer = null;
1315
+ }
1031
1316
  document.getElementById('view-semantic').classList.toggle('active', view === 'semantic');
1032
1317
  document.getElementById('view-agent').classList.toggle('active', view === 'agent');
1318
+ document.getElementById('view-tasks').classList.toggle('active', view === 'tasks');
1033
1319
  // Update nav tabs
1034
1320
  document.querySelectorAll('.nav-tab').forEach(function(tab) {
1035
1321
  tab.classList.toggle('active', tab.textContent.toLowerCase() === view);
1036
1322
  });
1323
+ if (view === 'tasks') Tasks.refreshList();
1037
1324
  updateHeaderButtons();
1038
1325
  }
1039
1326
 
@@ -1053,7 +1340,7 @@ function updateHeaderButtons() {
1053
1340
  // Hash routing
1054
1341
  window.addEventListener('hashchange', function() {
1055
1342
  var hash = location.hash.replace('#', '');
1056
- var view = (hash === 'agent') ? 'agent' : 'semantic';
1343
+ var view = (hash === 'agent') ? 'agent' : (hash === 'tasks') ? 'tasks' : 'semantic';
1057
1344
  if (view !== App.currentView) switchView(view);
1058
1345
  });
1059
1346
 
@@ -1251,7 +1538,10 @@ var Sem = {
1251
1538
  .then(function(data) {
1252
1539
  if (data.image) {
1253
1540
  if (data.image && data.image.indexOf('data:image/') === 0) {
1254
- self.screenshotHtml = '<img src="' + data.image + '" alt="screenshot">';
1541
+ var _simg = document.createElement('img');
1542
+ _simg.src = data.image;
1543
+ _simg.alt = 'screenshot';
1544
+ self.screenshotHtml = _simg.outerHTML;
1255
1545
  }
1256
1546
  if (self.rightTab === 'screenshot') self.renderRightContent();
1257
1547
  }
@@ -1616,6 +1906,9 @@ var Agent = {
1616
1906
  document.getElementById('btnStop').style.display = 'none';
1617
1907
  this.agentId = null;
1618
1908
  this.showTyping(false);
1909
+ document.getElementById('progressBar').classList.remove('active');
1910
+ document.getElementById('progressInfo').classList.remove('active');
1911
+ document.getElementById('progressFill').style.width = '0%';
1619
1912
  updateHeaderButtons();
1620
1913
  },
1621
1914
 
@@ -1632,6 +1925,7 @@ var Agent = {
1632
1925
  document.getElementById('agentViewElements').style.display = 'none';
1633
1926
  this.renderPreview();
1634
1927
  this.resetUI();
1928
+ ChatStore.clear();
1635
1929
  },
1636
1930
 
1637
1931
  showTyping: function(show) {
@@ -1691,9 +1985,26 @@ Agent.handleEvent = function(ev) {
1691
1985
  self.addMsg('system', 'Agent is waiting for your input...');
1692
1986
  self.showInputDialog(ev.requestId, ev.question, ev.fields);
1693
1987
  break;
1988
+ case 'progress':
1989
+ if (ev.progress) {
1990
+ var p = ev.progress;
1991
+ document.getElementById('progressFill').style.width = p.percent + '%';
1992
+ document.getElementById('progressPhase').textContent = p.phase || '';
1993
+ document.getElementById('progressPercent').textContent = p.percent + '%';
1994
+ document.getElementById('progressSteps').textContent =
1995
+ (p.stepsRemaining != null && p.stepsRemaining > 0) ? '~' + p.stepsRemaining + ' steps left' : '';
1996
+ document.getElementById('progressBar').classList.add('active');
1997
+ document.getElementById('progressInfo').classList.add('active');
1998
+ }
1999
+ break;
1694
2000
  case 'done':
1695
2001
  self.addMsg('done', ev.success ? (ev.result || 'Done') : (ev.error || 'Failed'), null, ev.success);
1696
- self.addMsg('system', 'Total steps: ' + ev.iterations);
2002
+ var summary = 'Total steps: ' + ev.iterations;
2003
+ if (ev.tokenUsage && ev.tokenUsage.total) {
2004
+ var tu = ev.tokenUsage;
2005
+ summary += ' | Tokens: ' + tu.total.toLocaleString() + ' (in: ' + tu.input.toLocaleString() + ' / out: ' + tu.output.toLocaleString() + ')';
2006
+ }
2007
+ self.addMsg('system', summary);
1697
2008
  if (self.currentTask) {
1698
2009
  self.conversationHistory.push({ role: 'user', content: self.currentTask });
1699
2010
  self.conversationHistory.push({ role: 'assistant', content: ev.result || ev.error || 'Done' });
@@ -1719,6 +2030,7 @@ Agent.addMsg = function(type, content, iteration, extra, extra2) {
1719
2030
 
1720
2031
  var el = document.createElement('div');
1721
2032
  el.className = 'msg';
2033
+ var self = this;
1722
2034
 
1723
2035
  switch (type) {
1724
2036
  case 'user':
@@ -1735,9 +2047,10 @@ Agent.addMsg = function(type, content, iteration, extra, extra2) {
1735
2047
  break;
1736
2048
  case 'tool_call':
1737
2049
  el.className = 'msg msg-tool-call';
1738
- el.innerHTML = this.stepTag(iteration) +
1739
- '<div class="tool-header"><span class="tool-icon">\u25B6</span> <span class="label">' + esc(content) + '</span></div>' +
1740
- '<pre>' + esc(JSON.stringify(extra, null, 2)) + '</pre>';
2050
+ var desc = self.humanizeToolCall(content, extra);
2051
+ el.innerHTML = self.stepTag(iteration) +
2052
+ '<div class="tool-header"><span class="tool-icon">\u25B6</span> <span class="tool-desc">' + esc(desc) + '</span></div>' +
2053
+ '<details class="tool-raw"><summary>Raw params</summary><pre>' + esc(JSON.stringify(extra, null, 2)) + '</pre></details>';
1741
2054
  break;
1742
2055
  case 'tool_result':
1743
2056
  el.className = 'msg msg-tool-result ' + (extra ? 'success' : 'fail');
@@ -1772,6 +2085,7 @@ Agent.addMsg = function(type, content, iteration, extra, extra2) {
1772
2085
 
1773
2086
  container.appendChild(el);
1774
2087
  container.scrollTop = container.scrollHeight;
2088
+ ChatStore.save();
1775
2089
  };
1776
2090
 
1777
2091
  Agent.toggleExpand = function(id, btn) {
@@ -1784,6 +2098,39 @@ Agent.stepTag = function(n) {
1784
2098
  if (n == null) return '';
1785
2099
  return '<span class="step-tag">Step ' + n + '</span>';
1786
2100
  };
2101
+
2102
+ Agent.humanizeToolCall = function(name, args) {
2103
+ args = args || {};
2104
+ var map = {
2105
+ navigate: function(a) { return 'Navigate: ' + (a.url || ''); },
2106
+ click: function(a) { return 'Click: ' + (a.elementId || a.element_id || ''); },
2107
+ type_text: function(a) { return 'Type: "' + (a.text || '').slice(0, 40) + '"'; },
2108
+ get_page_info: function() { return 'Get page elements'; },
2109
+ get_page_content: function() { return 'Extract page content'; },
2110
+ find_element: function(a) { return 'Search: "' + (a.query || '') + '"'; },
2111
+ screenshot: function() { return 'Take screenshot'; },
2112
+ scroll: function(a) { return 'Scroll ' + (a.direction || 'down'); },
2113
+ press_key: function(a) { return 'Press key: ' + (a.key || ''); },
2114
+ select_option: function(a) { return 'Select: ' + (a.value || a.text || ''); },
2115
+ fill_form: function(a) { return 'Fill form (' + (a.fields ? a.fields.length : 0) + ' fields)'; },
2116
+ click_and_wait: function(a) { return 'Click & wait: ' + (a.element_id || ''); },
2117
+ navigate_and_extract: function(a) { return 'Navigate & extract: ' + (a.url || ''); },
2118
+ hover: function(a) { return 'Hover: ' + (a.elementId || a.element_id || ''); },
2119
+ go_back: function() { return 'Go back'; },
2120
+ wait: function(a) { return 'Wait: ' + (a.condition || (a.ms + 'ms')); },
2121
+ wait_for_stable: function() { return 'Wait for page stable'; },
2122
+ done: function(a) { return 'Done: ' + (a.result || '').slice(0, 50); },
2123
+ set_value: function(a) { return 'Set value: ' + (a.elementId || a.element_id || ''); },
2124
+ upload_file: function() { return 'Upload file'; },
2125
+ execute_javascript: function() { return 'Execute JavaScript'; },
2126
+ create_tab: function() { return 'Create new tab'; },
2127
+ list_tabs: function() { return 'List tabs'; },
2128
+ switch_tab: function(a) { return 'Switch to tab ' + (a.tabId || ''); },
2129
+ close_tab: function() { return 'Close tab'; },
2130
+ };
2131
+ var fn = map[name];
2132
+ return fn ? fn(args) : name;
2133
+ };
1787
2134
  </script>
1788
2135
 
1789
2136
  <script>
@@ -1791,6 +2138,15 @@ Agent.stepTag = function(n) {
1791
2138
  Agent.formatMdResult = function(toolName, summary) {
1792
2139
  try {
1793
2140
  var obj = JSON.parse(summary);
2141
+ // Priority 1: use aiMarkdown from backend MCP enrichment layer
2142
+ if (typeof obj.aiMarkdown === 'string' && obj.aiMarkdown.length > 0) {
2143
+ return '## ' + toolName + '\n\n' + obj.aiMarkdown;
2144
+ }
2145
+ // Priority 2: use aiSummary as fallback
2146
+ if (typeof obj.aiSummary === 'string' && obj.aiSummary.length > 0) {
2147
+ return '## ' + toolName + '\n\n' + obj.aiSummary;
2148
+ }
2149
+ // Priority 3: manual formatting for tools without aiMarkdown
1794
2150
  if (toolName === 'get_page_info' && obj.elements) {
1795
2151
  var md = '## Page: ' + (obj.page?.title || '') + '\n\n';
1796
2152
  md += '**URL:** ' + (obj.page?.url || '') + '\n\n';
@@ -1801,33 +2157,10 @@ Agent.formatMdResult = function(toolName, summary) {
1801
2157
  });
1802
2158
  return md;
1803
2159
  }
1804
- if (toolName === 'get_page_content') {
1805
- var md = '## ' + (obj.title || 'Page Content') + '\n\n';
1806
- if (obj.sections && obj.sections.length > 0) {
1807
- obj.sections.forEach(function(s) {
1808
- var stars = s.attention >= 0.7 ? '\u2605\u2605\u2605' : s.attention >= 0.4 ? '\u2605\u2605' : '\u2605';
1809
- md += '**[' + stars + ']** ' + s.text + '\n\n';
1810
- });
1811
- } else {
1812
- md += '_No content sections extracted._\n';
1813
- }
1814
- return md;
1815
- }
1816
- if (toolName === 'navigate') {
2160
+ if (toolName === 'navigate' && obj.page) {
1817
2161
  return '## Navigate\n\n**URL:** ' + (obj.page?.url || '') + '\n\n**Title:** ' + (obj.page?.title || '');
1818
2162
  }
1819
- if (toolName === 'find_element') {
1820
- var md = '## Find Element: ' + (obj.query || '') + '\n\n';
1821
- if (obj.candidates && obj.candidates.length > 0) {
1822
- md += '| ID | Label | Score |\n|---|---|---|\n';
1823
- obj.candidates.forEach(function(c) {
1824
- md += '| `' + c.id + '` | ' + (c.label || '-') + ' | ' + c.score + ' |\n';
1825
- });
1826
- } else {
1827
- md += 'No matches found.';
1828
- }
1829
- return md;
1830
- }
2163
+ // Fallback: compact JSON
1831
2164
  return '## ' + toolName + '\n\n```json\n' + JSON.stringify(obj, null, 2) + '\n```';
1832
2165
  } catch (e) {
1833
2166
  return '## ' + toolName + '\n\n```\n' + summary + '\n```';
@@ -1837,11 +2170,16 @@ Agent.formatMdResult = function(toolName, summary) {
1837
2170
  Agent.refreshScreenshot = function() {
1838
2171
  var self = this;
1839
2172
  if (!self.sessionId) return;
2173
+ var body = document.getElementById('previewBody');
2174
+ if (self.previewMode === 'screenshot') body.classList.add('screenshot-loading');
1840
2175
  fetch('/v1/sessions/' + self.sessionId + '/screenshot')
1841
2176
  .then(function(r) { return r.json(); })
1842
2177
  .then(function(data) {
1843
2178
  if (data.image && data.image.indexOf('data:image/') === 0) {
1844
- self.latestScreenshotHtml = '<img src="' + data.image + '" alt="screenshot">';
2179
+ var _img = document.createElement('img');
2180
+ _img.src = data.image;
2181
+ _img.alt = 'screenshot';
2182
+ self.latestScreenshotHtml = _img.outerHTML;
1845
2183
  }
1846
2184
  return fetch('/v1/sessions/' + self.sessionId);
1847
2185
  }).then(function(r) { return r.json(); })
@@ -1850,8 +2188,13 @@ Agent.refreshScreenshot = function() {
1850
2188
  var tab = info.tabs.find(function(t) { return t.id === info.activeTabId; }) || info.tabs[0];
1851
2189
  document.getElementById('previewUrl').textContent = tab.url || '';
1852
2190
  }
1853
- if (self.previewMode === 'screenshot') self.renderPreview();
1854
- }).catch(function() {});
2191
+ body.classList.remove('screenshot-loading');
2192
+ if (self.previewMode === 'screenshot') {
2193
+ self.renderPreview();
2194
+ body.classList.add('screenshot-updated');
2195
+ setTimeout(function() { body.classList.remove('screenshot-updated'); }, 400);
2196
+ }
2197
+ }).catch(function() { body.classList.remove('screenshot-loading'); });
1855
2198
  };
1856
2199
  </script>
1857
2200
 
@@ -1950,6 +2293,217 @@ Agent.cancelInput = function(requestId) {
1950
2293
  };
1951
2294
  </script>
1952
2295
 
2296
+ <script>
2297
+ // ===== ChatStore: localStorage persistence =====
2298
+ var CHAT_STORE_KEY = 'ai_browser_chat_history';
2299
+ var ChatStore = {
2300
+ _saveTimer: null,
2301
+ save: function() {
2302
+ if (ChatStore._saveTimer) clearTimeout(ChatStore._saveTimer);
2303
+ ChatStore._saveTimer = setTimeout(ChatStore._doSave, 500);
2304
+ },
2305
+ _doSave: function() {
2306
+ ChatStore._saveTimer = null;
2307
+ try {
2308
+ var msgs = [];
2309
+ var container = document.getElementById('chatMessages');
2310
+ container.querySelectorAll('.msg').forEach(function(el) {
2311
+ var type = '';
2312
+ if (el.classList.contains('msg-user')) type = 'user';
2313
+ else if (el.classList.contains('msg-system')) type = 'system';
2314
+ else if (el.classList.contains('msg-thinking')) type = 'thinking';
2315
+ else if (el.classList.contains('msg-tool-call')) type = 'tool_call';
2316
+ else if (el.classList.contains('msg-tool-result')) type = 'tool_result';
2317
+ else if (el.classList.contains('msg-error')) type = 'error';
2318
+ else if (el.classList.contains('msg-done')) type = 'done';
2319
+ if (type) msgs.push({ type: type, html: el.outerHTML });
2320
+ });
2321
+ if (msgs.length === 0) return;
2322
+ if (msgs.length > 50) msgs = msgs.slice(-50);
2323
+ var data = {
2324
+ messages: msgs,
2325
+ conversationHistory: Agent.conversationHistory,
2326
+ savedAt: Date.now()
2327
+ };
2328
+ localStorage.setItem(CHAT_STORE_KEY, JSON.stringify(data));
2329
+ } catch(e) { /* quota exceeded or other error */ }
2330
+ },
2331
+ load: function() {
2332
+ try {
2333
+ var raw = localStorage.getItem(CHAT_STORE_KEY);
2334
+ if (!raw) return;
2335
+ var data = JSON.parse(raw);
2336
+ if (!data.messages || data.messages.length === 0) return;
2337
+ var container = document.getElementById('chatMessages');
2338
+ var ph = container.querySelector('.placeholder-text');
2339
+ if (ph) ph.remove();
2340
+ data.messages.forEach(function(m) {
2341
+ var wrapper = document.createElement('div');
2342
+ wrapper.innerHTML = DOMPurify.sanitize(m.html);
2343
+ if (wrapper.firstChild) container.appendChild(wrapper.firstChild);
2344
+ });
2345
+ if (data.conversationHistory) {
2346
+ Agent.conversationHistory = data.conversationHistory;
2347
+ }
2348
+ } catch(e) { /* parse error */ }
2349
+ },
2350
+ clear: function() {
2351
+ localStorage.removeItem(CHAT_STORE_KEY);
2352
+ }
2353
+ };
2354
+ </script>
2355
+
2356
+ <script>
2357
+ // ===== Tasks: SPA integration =====
2358
+ var Tasks = {
2359
+ pollTimer: null,
2360
+ currentTaskId: null,
2361
+
2362
+ refreshList: function() {
2363
+ var list = document.getElementById('tasksList');
2364
+ list.innerHTML = '<div class="placeholder-text">Loading...</div>';
2365
+ document.getElementById('taskDetail').style.display = 'none';
2366
+ document.getElementById('taskCreate').style.display = 'none';
2367
+ fetch('/v1/tasks').then(function(r) { return r.json(); }).then(function(data) {
2368
+ var tasks = data.tasks || [];
2369
+ if (tasks.length === 0) {
2370
+ list.innerHTML = '<div class="placeholder-text">No tasks yet. Click "+ New Task" to create one.</div>';
2371
+ return;
2372
+ }
2373
+ list.innerHTML = '';
2374
+ tasks.forEach(function(t) {
2375
+ var card = document.createElement('div');
2376
+ card.className = 'task-card';
2377
+ card.onclick = function() { Tasks.showResult(t.taskId); };
2378
+ var safeStatus = ['running','done','pending','failed'].indexOf(t.status) >= 0 ? t.status : 'pending';
2379
+ var statusCls = 'task-status ' + safeStatus;
2380
+ card.innerHTML = '<div class="' + statusCls + '">' + esc(t.status || 'unknown') + '</div>' +
2381
+ '<div style="font-weight:500;margin:6px 0">' + esc((t.goal || '').slice(0, 80)) + '</div>' +
2382
+ '<div style="font-size:12px;color:var(--text-muted)">' + new Date(t.createdAt).toLocaleString() + '</div>';
2383
+ list.appendChild(card);
2384
+ });
2385
+ }).catch(function() {
2386
+ list.innerHTML = '<div class="placeholder-text">Failed to load tasks.</div>';
2387
+ });
2388
+ },
2389
+
2390
+ showCreate: function() {
2391
+ document.getElementById('tasksList').style.display = 'none';
2392
+ document.getElementById('taskDetail').style.display = 'none';
2393
+ var panel = document.getElementById('taskCreate');
2394
+ panel.style.display = 'block';
2395
+ panel.innerHTML =
2396
+ '<h3 style="margin:0 0 16px">New Task</h3>' +
2397
+ '<div class="task-create-form">' +
2398
+ '<label>Goal</label>' +
2399
+ '<textarea id="tcGoal" rows="3" placeholder="Describe what you want the agent to do..."></textarea>' +
2400
+ '<label>Start URL (optional)</label>' +
2401
+ '<input type="text" id="tcUrl" placeholder="https://...">' +
2402
+ '<label>Max Steps</label>' +
2403
+ '<input type="number" id="tcMaxSteps" value="20" min="1" max="200">' +
2404
+ '<div style="display:flex;gap:8px;margin-top:12px">' +
2405
+ '<button class="btn btn-primary" onclick="Tasks.submitTask()">Run Task</button>' +
2406
+ '<button class="btn" onclick="Tasks.backToList()">Cancel</button>' +
2407
+ '</div>' +
2408
+ '</div>';
2409
+ },
2410
+
2411
+ backToList: function() {
2412
+ document.getElementById('taskCreate').style.display = 'none';
2413
+ document.getElementById('taskDetail').style.display = 'none';
2414
+ document.getElementById('tasksList').style.display = '';
2415
+ Tasks.refreshList();
2416
+ },
2417
+
2418
+ submitTask: function() {
2419
+ var goal = document.getElementById('tcGoal').value.trim();
2420
+ if (!goal) return;
2421
+ var url = document.getElementById('tcUrl').value.trim();
2422
+ var maxSteps = parseInt(document.getElementById('tcMaxSteps').value) || 20;
2423
+ if (url) goal = goal + '\nStart URL: ' + url;
2424
+
2425
+ var cfg = JSON.parse(localStorage.getItem(SETTINGS_KEY) || '{}');
2426
+ var body = {
2427
+ goal: goal,
2428
+ maxIterations: maxSteps,
2429
+ apiKey: cfg.apiKey || undefined,
2430
+ baseURL: cfg.baseURL || undefined,
2431
+ model: cfg.model || undefined,
2432
+ headless: cfg.headless !== undefined ? cfg.headless : true,
2433
+ };
2434
+
2435
+ fetch('/v1/tasks', {
2436
+ method: 'POST',
2437
+ headers: { 'Content-Type': 'application/json' },
2438
+ body: JSON.stringify(body),
2439
+ }).then(function(r) { return r.json(); }).then(function(data) {
2440
+ if (data.taskId) {
2441
+ Tasks.showResult(data.taskId);
2442
+ } else {
2443
+ alert('Failed to create task: ' + JSON.stringify(data));
2444
+ }
2445
+ }).catch(function(err) {
2446
+ alert('Error: ' + err.message);
2447
+ });
2448
+ },
2449
+
2450
+ showResult: function(taskId) {
2451
+ Tasks.currentTaskId = taskId;
2452
+ document.getElementById('tasksList').style.display = 'none';
2453
+ document.getElementById('taskCreate').style.display = 'none';
2454
+ var panel = document.getElementById('taskDetail');
2455
+ panel.style.display = 'block';
2456
+ panel.innerHTML = '<div class="placeholder-text">Loading task...</div>';
2457
+
2458
+ fetch('/v1/tasks/' + taskId).then(function(r) { return r.json(); }).then(function(data) {
2459
+ var safeStatus = ['running','done','pending','failed'].indexOf(data.status) >= 0 ? data.status : 'pending';
2460
+ var statusCls = 'task-status ' + safeStatus;
2461
+ var html = '<div style="display:flex;align-items:center;gap:12px;margin-bottom:16px">' +
2462
+ '<button class="btn" onclick="Tasks.backToList()">&larr; Back</button>' +
2463
+ '<span class="' + statusCls + '">' + esc(data.status || '') + '</span>' +
2464
+ '<span style="font-size:12px;color:var(--text-muted)">' + esc(taskId.slice(0, 8)) + '</span>' +
2465
+ '</div>';
2466
+ html += '<div style="margin-bottom:12px"><strong>Goal:</strong> ' + esc(data.goal || '(unknown)') + '</div>';
2467
+ if (data.result) {
2468
+ html += '<div style="margin-bottom:12px"><strong>Result:</strong></div>' +
2469
+ '<pre style="background:var(--bg-secondary);padding:12px;border-radius:6px;overflow:auto;max-height:300px">' +
2470
+ esc(typeof data.result === 'string' ? data.result : JSON.stringify(data.result, null, 2)) + '</pre>';
2471
+ }
2472
+ if (data.error) {
2473
+ html += '<div style="color:var(--danger)"><strong>Error:</strong> ' + esc(data.error) + '</div>';
2474
+ }
2475
+ html += '<div id="taskEvents" style="margin-top:16px"></div>';
2476
+ panel.innerHTML = html;
2477
+
2478
+ if (data.status === 'running') {
2479
+ Tasks.pollStatus(taskId);
2480
+ }
2481
+ }).catch(function() {
2482
+ panel.innerHTML = '<div class="placeholder-text">Failed to load task.</div>' +
2483
+ '<button class="btn" onclick="Tasks.backToList()" style="margin-top:12px">&larr; Back</button>';
2484
+ });
2485
+ },
2486
+
2487
+ pollStatus: function(taskId) {
2488
+ if (Tasks.pollTimer) clearInterval(Tasks.pollTimer);
2489
+ Tasks.pollTimer = setInterval(function() {
2490
+ if (Tasks.currentTaskId !== taskId) {
2491
+ clearInterval(Tasks.pollTimer);
2492
+ Tasks.pollTimer = null;
2493
+ return;
2494
+ }
2495
+ fetch('/v1/tasks/' + taskId).then(function(r) { return r.json(); }).then(function(data) {
2496
+ if (data.status !== 'running') {
2497
+ clearInterval(Tasks.pollTimer);
2498
+ Tasks.pollTimer = null;
2499
+ Tasks.showResult(taskId);
2500
+ }
2501
+ }).catch(function() {});
2502
+ }, 3000);
2503
+ }
2504
+ };
2505
+ </script>
2506
+
1953
2507
  <script>
1954
2508
  // ===== Keyboard shortcuts =====
1955
2509
  document.getElementById('taskInput').addEventListener('keypress', function(e) {
@@ -1987,11 +2541,9 @@ document.getElementById('typeValue').addEventListener('keypress', function(e) {
1987
2541
  // ========== Init ==========
1988
2542
  (function() {
1989
2543
  var hash = location.hash.replace('#', '');
1990
- if (hash === 'agent') {
1991
- switchView('agent');
1992
- } else {
1993
- switchView('semantic');
1994
- }
2544
+ var view = (hash === 'agent') ? 'agent' : (hash === 'tasks') ? 'tasks' : 'semantic';
2545
+ switchView(view);
2546
+ ChatStore.load();
1995
2547
  })();
1996
2548
  </script>
1997
2549
  </body>