linguclaw 0.4.0

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 (168) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +161 -0
  3. package/dist/agent-system.d.ts +196 -0
  4. package/dist/agent-system.d.ts.map +1 -0
  5. package/dist/agent-system.js +738 -0
  6. package/dist/agent-system.js.map +1 -0
  7. package/dist/alphabeta.d.ts +54 -0
  8. package/dist/alphabeta.d.ts.map +1 -0
  9. package/dist/alphabeta.js +193 -0
  10. package/dist/alphabeta.js.map +1 -0
  11. package/dist/browser.d.ts +62 -0
  12. package/dist/browser.d.ts.map +1 -0
  13. package/dist/browser.js +224 -0
  14. package/dist/browser.js.map +1 -0
  15. package/dist/cli.d.ts +7 -0
  16. package/dist/cli.d.ts.map +1 -0
  17. package/dist/cli.js +565 -0
  18. package/dist/cli.js.map +1 -0
  19. package/dist/code-parser.d.ts +39 -0
  20. package/dist/code-parser.d.ts.map +1 -0
  21. package/dist/code-parser.js +385 -0
  22. package/dist/code-parser.js.map +1 -0
  23. package/dist/config.d.ts +66 -0
  24. package/dist/config.d.ts.map +1 -0
  25. package/dist/config.js +232 -0
  26. package/dist/config.js.map +1 -0
  27. package/dist/core/engine.d.ts +359 -0
  28. package/dist/core/engine.d.ts.map +1 -0
  29. package/dist/core/engine.js +127 -0
  30. package/dist/core/engine.js.map +1 -0
  31. package/dist/daemon.d.ts +29 -0
  32. package/dist/daemon.d.ts.map +1 -0
  33. package/dist/daemon.js +212 -0
  34. package/dist/daemon.js.map +1 -0
  35. package/dist/email-receiver.d.ts +63 -0
  36. package/dist/email-receiver.d.ts.map +1 -0
  37. package/dist/email-receiver.js +553 -0
  38. package/dist/email-receiver.js.map +1 -0
  39. package/dist/git-integration.d.ts +180 -0
  40. package/dist/git-integration.d.ts.map +1 -0
  41. package/dist/git-integration.js +850 -0
  42. package/dist/git-integration.js.map +1 -0
  43. package/dist/inbox.d.ts +84 -0
  44. package/dist/inbox.d.ts.map +1 -0
  45. package/dist/inbox.js +198 -0
  46. package/dist/inbox.js.map +1 -0
  47. package/dist/index.d.ts +6 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +41 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/languages/cpp.d.ts +51 -0
  52. package/dist/languages/cpp.d.ts.map +1 -0
  53. package/dist/languages/cpp.js +930 -0
  54. package/dist/languages/cpp.js.map +1 -0
  55. package/dist/languages/csharp.d.ts +79 -0
  56. package/dist/languages/csharp.d.ts.map +1 -0
  57. package/dist/languages/csharp.js +1776 -0
  58. package/dist/languages/csharp.js.map +1 -0
  59. package/dist/languages/go.d.ts +50 -0
  60. package/dist/languages/go.d.ts.map +1 -0
  61. package/dist/languages/go.js +882 -0
  62. package/dist/languages/go.js.map +1 -0
  63. package/dist/languages/java.d.ts +47 -0
  64. package/dist/languages/java.d.ts.map +1 -0
  65. package/dist/languages/java.js +649 -0
  66. package/dist/languages/java.js.map +1 -0
  67. package/dist/languages/python.d.ts +47 -0
  68. package/dist/languages/python.d.ts.map +1 -0
  69. package/dist/languages/python.js +655 -0
  70. package/dist/languages/python.js.map +1 -0
  71. package/dist/languages/rust.d.ts +61 -0
  72. package/dist/languages/rust.d.ts.map +1 -0
  73. package/dist/languages/rust.js +1064 -0
  74. package/dist/languages/rust.js.map +1 -0
  75. package/dist/logger.d.ts +20 -0
  76. package/dist/logger.d.ts.map +1 -0
  77. package/dist/logger.js +133 -0
  78. package/dist/logger.js.map +1 -0
  79. package/dist/longterm-memory.d.ts +47 -0
  80. package/dist/longterm-memory.d.ts.map +1 -0
  81. package/dist/longterm-memory.js +300 -0
  82. package/dist/longterm-memory.js.map +1 -0
  83. package/dist/memory.d.ts +42 -0
  84. package/dist/memory.d.ts.map +1 -0
  85. package/dist/memory.js +274 -0
  86. package/dist/memory.js.map +1 -0
  87. package/dist/messaging.d.ts +103 -0
  88. package/dist/messaging.d.ts.map +1 -0
  89. package/dist/messaging.js +645 -0
  90. package/dist/messaging.js.map +1 -0
  91. package/dist/multi-provider.d.ts +69 -0
  92. package/dist/multi-provider.d.ts.map +1 -0
  93. package/dist/multi-provider.js +484 -0
  94. package/dist/multi-provider.js.map +1 -0
  95. package/dist/orchestrator.d.ts +65 -0
  96. package/dist/orchestrator.d.ts.map +1 -0
  97. package/dist/orchestrator.js +441 -0
  98. package/dist/orchestrator.js.map +1 -0
  99. package/dist/plugins.d.ts +52 -0
  100. package/dist/plugins.d.ts.map +1 -0
  101. package/dist/plugins.js +215 -0
  102. package/dist/plugins.js.map +1 -0
  103. package/dist/prism-orchestrator.d.ts +26 -0
  104. package/dist/prism-orchestrator.d.ts.map +1 -0
  105. package/dist/prism-orchestrator.js +191 -0
  106. package/dist/prism-orchestrator.js.map +1 -0
  107. package/dist/prism.d.ts +46 -0
  108. package/dist/prism.d.ts.map +1 -0
  109. package/dist/prism.js +188 -0
  110. package/dist/prism.js.map +1 -0
  111. package/dist/privacy.d.ts +23 -0
  112. package/dist/privacy.d.ts.map +1 -0
  113. package/dist/privacy.js +220 -0
  114. package/dist/privacy.js.map +1 -0
  115. package/dist/proactive.d.ts +30 -0
  116. package/dist/proactive.d.ts.map +1 -0
  117. package/dist/proactive.js +260 -0
  118. package/dist/proactive.js.map +1 -0
  119. package/dist/refactoring-engine.d.ts +100 -0
  120. package/dist/refactoring-engine.d.ts.map +1 -0
  121. package/dist/refactoring-engine.js +717 -0
  122. package/dist/refactoring-engine.js.map +1 -0
  123. package/dist/resilience.d.ts +43 -0
  124. package/dist/resilience.d.ts.map +1 -0
  125. package/dist/resilience.js +200 -0
  126. package/dist/resilience.js.map +1 -0
  127. package/dist/safety.d.ts +40 -0
  128. package/dist/safety.d.ts.map +1 -0
  129. package/dist/safety.js +133 -0
  130. package/dist/safety.js.map +1 -0
  131. package/dist/sandbox.d.ts +33 -0
  132. package/dist/sandbox.d.ts.map +1 -0
  133. package/dist/sandbox.js +173 -0
  134. package/dist/sandbox.js.map +1 -0
  135. package/dist/scheduler.d.ts +72 -0
  136. package/dist/scheduler.d.ts.map +1 -0
  137. package/dist/scheduler.js +374 -0
  138. package/dist/scheduler.js.map +1 -0
  139. package/dist/semantic-memory.d.ts +70 -0
  140. package/dist/semantic-memory.d.ts.map +1 -0
  141. package/dist/semantic-memory.js +430 -0
  142. package/dist/semantic-memory.js.map +1 -0
  143. package/dist/skills.d.ts +97 -0
  144. package/dist/skills.d.ts.map +1 -0
  145. package/dist/skills.js +575 -0
  146. package/dist/skills.js.map +1 -0
  147. package/dist/static/dashboard.html +853 -0
  148. package/dist/static/hub.html +772 -0
  149. package/dist/static/index.html +818 -0
  150. package/dist/static/logo.svg +24 -0
  151. package/dist/static/workflow-editor.html +913 -0
  152. package/dist/tools.d.ts +67 -0
  153. package/dist/tools.d.ts.map +1 -0
  154. package/dist/tools.js +303 -0
  155. package/dist/tools.js.map +1 -0
  156. package/dist/types.d.ts +295 -0
  157. package/dist/types.d.ts.map +1 -0
  158. package/dist/types.js +90 -0
  159. package/dist/types.js.map +1 -0
  160. package/dist/web.d.ts +76 -0
  161. package/dist/web.d.ts.map +1 -0
  162. package/dist/web.js +2139 -0
  163. package/dist/web.js.map +1 -0
  164. package/dist/workflow-engine.d.ts +114 -0
  165. package/dist/workflow-engine.d.ts.map +1 -0
  166. package/dist/workflow-engine.js +855 -0
  167. package/dist/workflow-engine.js.map +1 -0
  168. package/package.json +77 -0
@@ -0,0 +1,853 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>LinguClaw</title>
7
+ <style>
8
+ :root{--bg0:#0a0a0b;--bg1:#121214;--bg2:#1a1a1c;--bg3:#222224;--border:#2e2e30;--accent:#5b5bd6;--accent2:#4a4ac2;--accent3:#6c6ce0;--glow:rgba(91,91,214,.15);--green:#4ade80;--red:#f87171;--yellow:#fbbf24;--cyan:#22d3ee;--orange:#fb923c;--text:#f0f0f1;--dim:#9ca3af;--muted:#6b7280;--r:8px;--r2:12px}
9
+ *{box-sizing:border-box;margin:0;padding:0}
10
+ body{font-family:'Inter','SF Pro Display',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:var(--bg0);color:var(--text);height:100vh;overflow:hidden;display:flex;flex-direction:column}
11
+ ::-webkit-scrollbar{width:5px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
12
+ ::selection{background:var(--accent);color:#fff}
13
+ /* Smooth view transitions */
14
+ .view{flex:1;display:flex;flex-direction:column;overflow:hidden;opacity:0;transform:translateX(-8px);transition:opacity .3s ease,transform .3s ease;position:absolute;width:100%;height:100%;pointer-events:none}
15
+ .view.active{opacity:1;transform:translateX(0);position:relative;pointer-events:all}
16
+ .main{flex:1;position:relative;overflow:hidden;background:var(--bg0)}
17
+ .top{display:flex;align-items:center;justify-content:space-between;height:52px;padding:0 20px;background:linear-gradient(180deg,var(--bg1),var(--bg0));border-bottom:1px solid var(--border);flex-shrink:0}
18
+ .top-left{display:flex;align-items:center;gap:10px}
19
+ .logo{width:28px;height:28px;color:var(--accent3);opacity:.9}
20
+ .brand{font-weight:700;font-size:15px;letter-spacing:-.3px;color:var(--dim)}.brand span{color:var(--text)}
21
+ .top-right{display:flex;align-items:center;gap:8px}
22
+ .badge{display:flex;align-items:center;gap:5px;padding:4px 12px;background:var(--bg2);border:1px solid var(--border);border-radius:16px;font-size:11px;color:var(--dim);font-weight:500}
23
+ .dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}.dot.on{background:var(--green)}.dot.off{background:var(--red)}
24
+ .wrap{display:flex;flex:1;overflow:hidden}
25
+ .side{width:230px;background:var(--bg1);border-right:1px solid var(--border);display:flex;flex-direction:column;flex-shrink:0}
26
+ .nav{padding:12px 10px}
27
+ .ni{display:flex;align-items:center;gap:9px;padding:9px 12px;border-radius:8px;cursor:pointer;font-size:13px;color:var(--dim);transition:background .15s ease,color .15s ease;margin-bottom:2px;user-select:none;font-weight:500;border:1px solid transparent}
28
+ .ni:hover{background:var(--bg2);color:var(--text)}
29
+ .ni.a{background:var(--bg3);color:var(--text);border-color:var(--border)}
30
+ .ni .ic{width:18px;height:18px;flex-shrink:0;opacity:.6}
31
+ .ni.a .ic,.ni:hover .ic{opacity:1}
32
+ .ni .cnt{margin-left:auto;font-size:10px;background:var(--bg3);padding:1px 6px;border-radius:8px;color:var(--muted)}
33
+ .ni.a .cnt{background:var(--accent);color:#fff}
34
+ .sep{height:1px;background:var(--border);margin:10px 14px}
35
+ .nav-label{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.8px;color:var(--muted);padding:6px 14px 4px}
36
+ .side-info{padding:10px 14px;margin-top:auto;border-top:1px solid var(--border);background:var(--bg0)}
37
+ .si-row{display:flex;justify-content:space-between;font-size:11px;color:var(--muted);margin-bottom:4px}
38
+ .si-val{color:var(--dim);font-weight:500}
39
+ .panel-head{padding:16px 20px;border-bottom:1px solid var(--border);background:var(--bg1)}
40
+ .panel-head h2{font-size:16px;font-weight:600;display:flex;align-items:center;gap:8px}
41
+ .panel-head p{font-size:12px;color:var(--muted);margin-top:2px}
42
+ .btn{padding:8px 16px;border:none;border-radius:8px;cursor:pointer;font-size:13px;font-weight:600;transition:all .2s ease;display:inline-flex;align-items:center;gap:6px}
43
+ .btn:disabled{opacity:.4;cursor:default}
44
+ .btn-p{background:var(--accent);color:#fff}.btn-p:hover:not(:disabled){background:var(--accent2);transform:translateY(-1px);box-shadow:0 4px 12px var(--glow)}
45
+ .btn-g{background:transparent;color:var(--dim);border:1px solid var(--border)}.btn-g:hover{color:var(--text);background:var(--bg2);border-color:var(--accent);transform:translateY(-1px)}
46
+ .btn-d{background:rgba(239,68,68,.1);color:var(--red);border:1px solid rgba(239,68,68,.2)}.btn-d:hover{background:rgba(239,68,68,.2)}
47
+ .btn-sm{padding:5px 10px;font-size:11px;border-radius:6px}
48
+ .inp{padding:9px 14px;background:var(--bg2);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:13px;outline:none;transition:border .15s;width:100%}
49
+ .inp:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--glow)}
50
+ .inp::placeholder{color:var(--muted)}
51
+ select.inp{appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%2364748b'%3E%3Cpath d='M6 8L1 3h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 10px center;padding-right:28px}
52
+ .lbl{display:block;font-size:11px;color:var(--dim);margin-bottom:4px;font-weight:600;letter-spacing:.3px}
53
+ .toast{position:fixed;top:16px;right:16px;padding:10px 16px;border-radius:8px;font-size:13px;font-weight:500;z-index:9999;opacity:0;transform:translateY(-8px);transition:all .25s;pointer-events:none}
54
+ .toast.show{opacity:1;transform:translateY(0)}.toast.ok{background:#0d2818;color:var(--green);border:1px solid rgba(34,197,94,.3)}.toast.err{background:#2a0f0f;color:var(--red);border:1px solid rgba(239,68,68,.3)}.toast.info{background:#0f1b2d;color:var(--accent3);border:1px solid rgba(59,130,246,.3)}
55
+ .modal-bg{display:none;position:fixed;inset:0;background:rgba(0,0,0,.6);backdrop-filter:blur(4px);z-index:100;align-items:center;justify-content:center;opacity:0;transition:opacity .2s ease}
56
+ .modal-bg.open{display:flex;opacity:1}
57
+ .modal{background:var(--bg1);border:1px solid var(--border);border-radius:var(--r2);padding:24px;width:90%;max-width:480px;box-shadow:0 20px 60px rgba(0,0,0,.5);transform:scale(.95) translateY(10px);transition:transform .25s ease,opacity .25s ease;opacity:0}
58
+ .modal-bg.open .modal{transform:scale(1) translateY(0);opacity:1}
59
+ .modal h3{font-size:16px;margin-bottom:16px;display:flex;align-items:center;gap:8px}
60
+ .modal .fld{margin-bottom:12px}
61
+ .modal .acts{display:flex;gap:8px;justify-content:flex-end;margin-top:20px}
62
+ /* Chat */
63
+ .chat-box{flex:1;overflow-y:auto;padding:20px;display:flex;flex-direction:column;gap:12px}
64
+ .msg{max-width:72%;padding:12px 16px;border-radius:var(--r2);font-size:13px;line-height:1.6;white-space:pre-wrap;word-break:break-word;animation:mi .2s ease}
65
+ @keyframes mi{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
66
+ .msg.user{align-self:flex-end;background:var(--bg3);color:var(--text);border:1px solid var(--border);border-bottom-right-radius:4px}
67
+ .msg.bot{align-self:flex-start;background:var(--bg2);color:var(--text);border-bottom-left-radius:4px;border:1px solid var(--border)}
68
+ .msg.action{align-self:flex-start;background:rgba(22,163,74,.06);color:var(--text);border-bottom-left-radius:4px;border:1px solid rgba(22,163,74,.25)}
69
+ .msg.err{color:var(--red);background:rgba(239,68,68,.06);border:1px solid rgba(239,68,68,.2);align-self:flex-start}
70
+ .msg.info{color:var(--accent3);background:rgba(37,99,235,.06);border:1px solid rgba(37,99,235,.2);align-self:flex-start}
71
+ .msg-head{display:flex;align-items:center;gap:6px;margin-bottom:6px;font-size:11px;flex-wrap:wrap}
72
+ .msg-who{font-weight:700;font-size:12px}
73
+ .msg.user .msg-who{color:var(--dim)}
74
+ .msg.bot .msg-who{color:var(--dim)}
75
+ .msg.action .msg-who{color:#16a34a}
76
+ .msg.err .msg-who{color:var(--red)}
77
+ .msg-time{color:var(--muted);font-size:10px;margin-left:auto}
78
+ .msg-model{font-size:9px;color:var(--muted);background:var(--bg0);padding:1px 6px;border-radius:4px}
79
+ .msg-badge{font-size:9px;padding:2px 8px;border-radius:4px;font-weight:700;text-transform:uppercase;letter-spacing:.3px}
80
+ .msg-badge.ok{background:rgba(22,163,74,.15);color:#16a34a}
81
+ .msg-badge.fail{background:rgba(239,68,68,.15);color:var(--red)}
82
+ .msg-body{font-size:13px;line-height:1.6}
83
+ .chat-bar{display:flex;gap:8px;padding:14px 20px;border-top:1px solid var(--border);background:var(--bg1)}
84
+ .typing{display:none;padding:4px 20px;font-size:12px;color:var(--muted);align-items:center;gap:6px}
85
+ .typing.show{display:flex}
86
+ .typing .dots span{display:inline-block;width:4px;height:4px;background:var(--accent3);border-radius:50%;animation:bn .6s infinite alternate}
87
+ .typing .dots span:nth-child(2){animation-delay:.15s}.typing .dots span:nth-child(3){animation-delay:.3s}
88
+ @keyframes bn{to{transform:translateY(-4px);opacity:.4}}
89
+ /* Task */
90
+ .task-out{flex:1;overflow-y:auto;padding:20px;font-family:'JetBrains Mono','Fira Code',monospace;font-size:12px;line-height:1.7;white-space:pre-wrap;color:var(--dim)}
91
+ /* Memory */
92
+ .mem-list{flex:1;overflow-y:auto;padding:16px 20px}
93
+ .mi2{display:flex;align-items:center;padding:10px 14px;background:var(--bg2);border:1px solid var(--border);border-radius:var(--r);margin-bottom:6px;font-size:12px;gap:10px;transition:border .15s}
94
+ .mi2:hover{border-color:var(--accent)}
95
+ .mk{color:var(--cyan);font-weight:700;min-width:80px;max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
96
+ .mv{color:var(--dim);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
97
+ .mc{font-size:10px;color:var(--muted);background:var(--bg3);padding:2px 8px;border-radius:6px;font-weight:600}
98
+ .mem-bar{display:flex;gap:6px;padding:12px 20px;border-top:1px solid var(--border);background:var(--bg1)}
99
+ /* Skills */
100
+ .skills-head{padding:16px 20px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;background:var(--bg1)}
101
+ .skills-head h2{font-size:16px;font-weight:600;display:flex;align-items:center;gap:8px}
102
+ .skills-body{flex:1;overflow-y:auto;padding:16px 20px;display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:12px;align-content:start}
103
+ .sk{background:var(--bg2);border:1px solid var(--border);border-radius:var(--r2);padding:16px;transition:transform .25s ease,box-shadow .25s ease,border-color .25s ease,background .25s ease;position:relative}
104
+ .sk:hover{border-color:var(--accent);transform:translateY(-2px);box-shadow:0 8px 24px rgba(0,0,0,.3)}
105
+ .sk-top{display:flex;align-items:center;gap:8px;margin-bottom:6px}
106
+ .sk-ic{width:34px;height:34px;border-radius:8px;display:flex;align-items:center;justify-content:center;background:var(--bg3);color:var(--dim)}
107
+ .sk-ic svg{width:16px;height:16px}
108
+ .sk-name{font-weight:600;font-size:14px}.sk-type{font-size:10px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px}
109
+ .sk-desc{font-size:12px;color:var(--dim);margin-bottom:10px;line-height:1.5}
110
+ .sk-foot{display:flex;align-items:center;justify-content:space-between}
111
+ .sk-st{font-size:11px;font-weight:600;display:flex;align-items:center;gap:4px}
112
+ .sk-st.on{color:var(--green)}.sk-st.off{color:var(--muted)}
113
+ .sv{width:16px;height:16px;display:inline-block;vertical-align:middle}
114
+ .sk-st .sd{width:6px;height:6px;border-radius:50%;background:currentColor}
115
+ .sk-del{position:absolute;top:8px;right:8px;display:none}
116
+ .sk:hover .sk-del{display:block}
117
+ /* Scheduler */
118
+ .sched-body{flex:1;overflow-y:auto;padding:16px 20px}
119
+ .job{display:flex;align-items:center;gap:12px;padding:12px 16px;background:var(--bg2);border:1px solid var(--border);border-radius:var(--r2);margin-bottom:8px;transition:border .15s}
120
+ .job:hover{border-color:var(--accent)}
121
+ .job-ic{width:34px;height:34px;border-radius:8px;background:var(--bg3);display:flex;align-items:center;justify-content:center;flex-shrink:0;color:var(--dim)}
122
+ .job-ic svg{width:16px;height:16px}
123
+ .job-info{flex:1;min-width:0}
124
+ .job-name{font-weight:600;font-size:13px}.job-meta{font-size:11px;color:var(--muted);margin-top:2px;display:flex;gap:10px}
125
+ .job-cmd{font-size:11px;color:var(--dim);margin-top:4px;font-family:monospace;background:var(--bg0);padding:4px 8px;border-radius:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
126
+ .job-acts{display:flex;gap:4px;flex-shrink:0}
127
+ /* Browser */
128
+ .brow-bar{display:flex;gap:8px;padding:14px 20px;border-bottom:1px solid var(--border);background:var(--bg1);align-items:center}
129
+ .url-w{flex:1;display:flex;align-items:center;background:var(--bg2);border:1px solid var(--border);border-radius:8px;overflow:hidden;transition:border .15s}
130
+ .url-w:focus-within{border-color:var(--accent);box-shadow:0 0 0 3px var(--glow)}
131
+ .url-w .ui{padding:0 10px;color:var(--muted);font-size:13px}
132
+ .url-w input{flex:1;background:none;border:none;color:var(--text);font-size:13px;padding:9px 10px 9px 0;outline:none}
133
+ .brow-tools{display:flex;gap:6px;padding:8px 20px;border-bottom:1px solid var(--border);background:var(--bg1)}
134
+ .brow-body{flex:1;overflow-y:auto;padding:20px}
135
+ .brow-t{font-size:18px;font-weight:600;margin-bottom:4px}
136
+ .brow-u{font-size:11px;color:var(--muted);margin-bottom:14px;word-break:break-all}
137
+ .brow-c{font-size:12px;color:var(--dim);line-height:1.65;white-space:pre-wrap;background:var(--bg2);border:1px solid var(--border);border-radius:var(--r);padding:14px;max-height:50vh;overflow-y:auto}
138
+ .brow-l{margin-top:16px}.brow-l h4{font-size:12px;font-weight:600;color:var(--dim);margin-bottom:8px}
139
+ .brow-l a{display:inline-block;font-size:11px;color:var(--accent3);text-decoration:none;padding:4px 10px;background:var(--bg2);border:1px solid var(--border);border-radius:6px;margin:2px;cursor:pointer}
140
+ .brow-l a:hover{background:var(--bg3);border-color:var(--accent)}
141
+ /* Settings */
142
+ .settings-body{flex:1;overflow-y:auto;padding:20px}
143
+ .sc{background:var(--bg2);border:1px solid var(--border);border-radius:var(--r2);padding:20px;margin-bottom:14px;max-width:640px}
144
+ .sc h3{font-size:14px;font-weight:600;margin-bottom:14px;display:flex;align-items:center;gap:8px}
145
+ .sr{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:10px}
146
+ .sf{margin-bottom:10px}
147
+ .sh{font-size:10px;color:var(--muted);margin-top:3px}
148
+ .sa{display:flex;gap:8px;margin-top:16px}
149
+ .es{text-align:center;padding:60px 20px;color:var(--muted)}.es .ei{font-size:40px;margin-bottom:12px;opacity:.5}.es p{font-size:13px;margin-bottom:16px}
150
+ /* Workflow List */
151
+ .wf-item{display:flex;align-items:center;gap:14px;padding:14px 18px;background:var(--bg2);border:1px solid var(--border);border-radius:var(--r2);margin-bottom:8px;transition:transform .15s,border .15s,box-shadow .15s;cursor:pointer}
152
+ .wf-item:hover{border-color:var(--accent);transform:translateY(-1px);box-shadow:0 4px 16px rgba(0,0,0,.3)}
153
+ .wf-item .wf-icon{width:40px;height:40px;border-radius:10px;background:linear-gradient(135deg,rgba(91,91,214,.15),rgba(91,91,214,.05));display:flex;align-items:center;justify-content:center;flex-shrink:0;color:var(--accent3)}
154
+ .wf-item .wf-icon svg{width:18px;height:18px}
155
+ .wf-item .wf-info{flex:1;min-width:0}
156
+ .wf-item .wf-name{font-weight:600;font-size:14px;margin-bottom:3px}
157
+ .wf-item .wf-desc{font-size:12px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
158
+ .wf-item .wf-meta{display:flex;gap:12px;font-size:11px;color:var(--muted);margin-top:4px}
159
+ .wf-item .wf-meta span{display:flex;align-items:center;gap:3px}
160
+ .wf-item .wf-badge{font-size:10px;padding:3px 8px;border-radius:4px;font-weight:600}
161
+ .wf-item .wf-badge.active{background:rgba(74,222,128,.1);color:var(--green)}
162
+ .wf-item .wf-badge.draft{background:var(--bg3);color:var(--muted)}
163
+ .wf-item .wf-acts{display:flex;gap:4px;flex-shrink:0}
164
+ @keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}
165
+ /* Home Dashboard */
166
+ .home-wrap{flex:1;overflow-y:auto;padding:20px}
167
+ .home-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:16px;max-width:1400px;margin:0 auto}
168
+ .home-card{background:var(--bg2);border:1px solid var(--border);border-radius:var(--r2);padding:16px;transition:transform .2s ease,box-shadow .2s ease,background .2s ease}
169
+ .home-card:hover{transform:translateY(-2px);box-shadow:0 8px 24px rgba(0,0,0,.4);background:var(--bg3)}
170
+ .home-card h3{font-size:13px;font-weight:600;color:var(--dim);text-transform:uppercase;letter-spacing:.5px;margin-bottom:12px;display:flex;align-items:center;gap:6px}
171
+ .home-card h3 svg{width:14px;height:14px;opacity:.7}
172
+ /* Operations Overview */
173
+ .ops-card{grid-column:span 12;display:flex;align-items:center;gap:16px;padding:20px}
174
+ .ops-status{width:48px;height:48px;border-radius:50%;background:var(--bg3);display:flex;align-items:center;justify-content:center;color:var(--green)}
175
+ .ops-status svg{width:24px;height:24px}
176
+ .ops-info{flex:1}
177
+ .ops-info h2{font-size:18px;font-weight:600;margin-bottom:4px}
178
+ .ops-info p{font-size:13px;color:var(--dim)}
179
+ .ops-stats{display:flex;gap:24px}
180
+ .ops-stat{text-align:center}
181
+ .ops-stat .val{font-size:20px;font-weight:700;color:var(--text)}
182
+ .ops-stat .lbl{font-size:11px;color:var(--muted);text-transform:uppercase}
183
+ .ops-actions{display:flex;gap:8px}
184
+ /* System Health Cards */
185
+ .health-card{grid-column:span 3}
186
+ .health-card.h-wide{grid-column:span 6}
187
+ .health-h{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}
188
+ .health-h .title{font-size:14px;font-weight:600}
189
+ .health-h .status{font-size:11px;padding:2px 8px;border-radius:4px;font-weight:600}
190
+ .health-h .status.ok{background:rgba(52,211,153,.1);color:var(--green)}
191
+ .health-h .status.warn{background:rgba(251,191,36,.1);color:var(--yellow)}
192
+ .health-h .status.err{background:rgba(248,113,113,.1);color:var(--red)}
193
+ .health-body{font-size:12px;color:var(--dim);line-height:1.6}
194
+ .health-body .big{font-size:24px;font-weight:700;color:var(--text);margin-bottom:4px}
195
+ .health-body .detail{display:flex;align-items:center;gap:6px;margin-top:4px}
196
+ .health-body .detail svg{width:12px;height:12px;opacity:.5}
197
+ /* Activity Feed */
198
+ .activity-card{grid-column:span 4}
199
+ .act-list{max-height:240px;overflow-y:auto}
200
+ .act-item{display:flex;gap:10px;padding:8px 0;border-bottom:1px solid var(--border)}
201
+ .act-item:last-child{border-bottom:none}
202
+ .act-icon{width:28px;height:28px;border-radius:6px;background:var(--bg3);display:flex;align-items:center;justify-content:center;flex-shrink:0}
203
+ .act-icon svg{width:14px;height:14px;color:var(--dim)}
204
+ .act-content{flex:1;min-width:0}
205
+ .act-title{font-size:12px;font-weight:500;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
206
+ .act-meta{font-size:11px;color:var(--muted);margin-top:2px}
207
+ /* Topology */
208
+ .topo-card{grid-column:span 8}
209
+ .topo-vis{height:200px;background:var(--bg3);border-radius:var(--r);display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden}
210
+ .topo-node{position:absolute;padding:8px 12px;background:var(--bg2);border:1px solid var(--border);border-radius:8px;font-size:12px;font-weight:500}
211
+ .topo-node.center{top:50%;left:50%;transform:translate(-50%,-50%);border-color:var(--accent);background:rgba(99,102,241,.1)}
212
+ .topo-line{position:absolute;background:var(--border)}
213
+ /* Quick Actions */
214
+ .quick-card{grid-column:span 4}
215
+ .quick-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px}
216
+ .quick-btn{padding:12px;background:var(--bg3);border:1px solid var(--border);border-radius:8px;cursor:pointer;transition:all .15s;text-align:left}
217
+ .quick-btn:hover{background:var(--bg2);border-color:var(--accent)}
218
+ .quick-btn svg{width:16px;height:16px;color:var(--dim);margin-bottom:6px}
219
+ .quick-btn .lbl{font-size:12px;font-weight:500;color:var(--text)}
220
+ .quick-btn .sub{font-size:11px;color:var(--muted);margin-top:2px}
221
+ </style>
222
+ </head>
223
+ <body>
224
+ <div id="toast" class="toast"></div>
225
+ <div class="top">
226
+ <div class="top-left"><svg class="logo" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 3c-1.5 0-2.7.5-3.5 1.3C7.7 5.1 7 6.3 7 8c0 1.3.4 2.4 1 3.2V13c0 1.5-.5 2.5-1.2 3.2-.4.4-.4 1 0 1.4.4.4 1 .4 1.4 0 .9-.9 1.5-2.2 1.7-3.7.5.2 1 .3 1.6.3h.9c.6 0 1.1-.1 1.6-.3.2 1.5.8 2.8 1.7 3.7.4.4 1 .4 1.4 0 .4-.4.4-1 0-1.4-.7-.7-1.2-1.7-1.2-3.2v-1.8c.6-.8 1-1.9 1-3.2 0-1.7-.7-2.9-1.5-3.7C14.7 3.5 13.5 3 12 3z"/><path d="M5 9c-1.5 0-3 .5-4 1.5C.5 11.5 0 12.5 0 14c0 1.5.5 2.5 1.2 3.2.4.4 1 .4 1.4 0 .4-.4.4-1 0-1.4-.4-.4-.6-1-.6-1.8 0-.8.3-1.4.7-1.8.4-.4 1-.6 1.8-.6"/><path d="M19 9c1.5 0 3 .5 4 1.5.5.5 1 1.5 1 3 0 1.5-.5 2.5-1.2 3.2-.4.4-1 .4-1.4 0-.4-.4-.4-1 0-1.4.4-.4.6-1 .6-1.8 0-.8-.3-1.4-.7-1.8-.4-.4-1-.6-1.8-.6"/><path d="M4 20c-.8 0-1.5.5-1.8 1.2-.2.5.1 1.1.6 1.3.5.2 1.1-.1 1.3-.6.1-.2.3-.4.6-.4.3 0 .5.2.6.4.2.5.8.8 1.3.6.5-.2.8-.8.6-1.3C7 20.5 6.3 20 5.5 20H4z"/><path d="M16 20c-.8 0-1.5.5-1.8 1.2-.2.5.1 1.1.6 1.3.5.2 1.1-.1 1.3-.6.1-.2.3-.4.6-.4.3 0 .5.2.6.4.2.5.8.8 1.3.6.5-.2.8-.8.6-1.3-.3-.7-1-1.2-1.8-1.2h-1.5z"/></svg><div class="brand">Lingu<span>Claw</span></div></div>
227
+ <div class="top-right">
228
+ <div class="badge" id="modelBadge">loading...</div>
229
+ <div class="badge"><div class="dot on" id="wsDot"></div><span id="wsLabel">Connected</span></div>
230
+ </div>
231
+ </div>
232
+ <div class="wrap">
233
+ <div class="side">
234
+ <div class="nav">
235
+ <div class="nav-label">Main</div>
236
+ <div class="ni a" data-v="home"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg> Home</div>
237
+ <div class="ni" data-v="inbox"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 17H2a3 3 0 0 0 3-3V9a7 7 0 0 1 14 0v5a3 3 0 0 0 3 3zm-8.27 4a2 2 0 0 1-3.46 0"/></svg> Inbox <span class="cnt" id="cntInbox" style="display:none">0</span></div>
238
+ <div class="ni" data-v="chat"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg> Chat</div>
239
+ <div class="ni" data-v="task"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg> Tasks</div>
240
+ <div class="ni" data-v="memory"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg> Memory <span class="cnt" id="cntMem">0</span></div>
241
+ <div class="sep"></div>
242
+ <div class="nav-label">Tools</div>
243
+ <div class="ni" data-v="skills"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="7" width="20" height="14" rx="2" ry="2"/><path d="M16 7V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2"/></svg> Skills <span class="cnt" id="cntSk">0</span></div>
244
+ <div class="ni" data-v="workflows"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><path d="M6 21V9a9 9 0 0 0 9 9"/></svg> Workflows <span class="cnt" id="cntWf">0</span></div>
245
+ <div class="ni" data-v="sched"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg> Scheduler <span class="cnt" id="cntJob">0</span></div>
246
+ <div class="ni" data-v="browser"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg> Browser</div>
247
+ <div class="sep"></div>
248
+ <div class="ni" data-v="settings"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg> Settings</div>
249
+ </div>
250
+ <div class="side-info">
251
+ <div class="si-row"><span>Provider</span><span class="si-val" id="siProv">-</span></div>
252
+ <div class="si-row"><span>Uptime</span><span class="si-val" id="siUp">0s</span></div>
253
+ <div class="si-row"><span>Connections</span><span class="si-val" id="siConn">0</span></div>
254
+ </div>
255
+ </div>
256
+ <div class="main">
257
+ <!-- HOME -->
258
+ <div class="view active" id="v-home">
259
+ <div class="home-wrap">
260
+ <div class="home-grid">
261
+ <!-- Operations Overview -->
262
+ <div class="home-card ops-card">
263
+ <div class="ops-status"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg></div>
264
+ <div class="ops-info">
265
+ <h2>Operations Overview</h2>
266
+ <p>System operational. All services running normally.</p>
267
+ </div>
268
+ <div class="ops-stats">
269
+ <div class="ops-stat"><div class="val" id="hSess">0</div><div class="lbl">Sessions</div></div>
270
+ <div class="ops-stat"><div class="val" id="hAlerts">0</div><div class="lbl">Alerts</div></div>
271
+ <div class="ops-stat"><div class="val" id="hTasks">0</div><div class="lbl">Tasks</div></div>
272
+ </div>
273
+ <div class="ops-actions">
274
+ <button class="btn btn-p" onclick="switchView('chat')">New Session</button>
275
+ <button class="btn btn-g" onclick="switchView('task')">Run Task</button>
276
+ </div>
277
+ </div>
278
+ <!-- System Health Cards -->
279
+ <div class="home-card health-card">
280
+ <div class="health-h"><span class="title">Gateway</span><span class="status ok">Online</span></div>
281
+ <div class="health-body">
282
+ <div class="big" id="hUptime">0m</div>
283
+ <div class="detail"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg> Uptime</div>
284
+ </div>
285
+ </div>
286
+ <div class="home-card health-card">
287
+ <div class="health-h"><span class="title">LLM Provider</span><span class="status ok" id="hProvStatus">Active</span></div>
288
+ <div class="health-body">
289
+ <div class="big" id="hModel">-</div>
290
+ <div class="detail"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg> Model ready</div>
291
+ </div>
292
+ </div>
293
+ <div class="home-card health-card">
294
+ <div class="health-h"><span class="title">Scheduler</span><span class="status ok" id="hSchedStatus">Active</span></div>
295
+ <div class="health-body">
296
+ <div class="big" id="hJobs">0</div>
297
+ <div class="detail"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg> Jobs scheduled</div>
298
+ </div>
299
+ </div>
300
+ <div class="home-card health-card">
301
+ <div class="health-h"><span class="title">Memory</span><span class="status ok">Healthy</span></div>
302
+ <div class="health-body">
303
+ <div class="big" id="hMem">0</div>
304
+ <div class="detail"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg> Entries stored</div>
305
+ </div>
306
+ </div>
307
+ <!-- Skills Status -->
308
+ <div class="home-card health-card h-wide">
309
+ <div class="health-h"><span class="title">Active Integrations</span><span class="status ok" id="hSkillsStatus">0/0</span></div>
310
+ <div class="health-body" style="display:flex;gap:16px;flex-wrap:wrap" id="hSkillsList">
311
+ <span style="color:var(--muted)">No integrations configured</span>
312
+ </div>
313
+ </div>
314
+ <!-- Recent Activity -->
315
+ <div class="home-card activity-card">
316
+ <h3><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg> Recent Activity</h3>
317
+ <div class="act-list" id="hActivity">
318
+ <div class="act-item">
319
+ <div class="act-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></div>
320
+ <div class="act-content"><div class="act-title">System initialized</div><div class="act-meta">Just now</div></div>
321
+ </div>
322
+ </div>
323
+ </div>
324
+ <!-- Quick Actions -->
325
+ <div class="home-card quick-card">
326
+ <h3><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg> Quick Actions</h3>
327
+ <div class="quick-grid">
328
+ <div class="quick-btn" onclick="switchView('chat')">
329
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
330
+ <div class="lbl">New Chat</div>
331
+ <div class="sub">Start conversation</div>
332
+ </div>
333
+ <div class="quick-btn" onclick="switchView('task')">
334
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>
335
+ <div class="lbl">Run Task</div>
336
+ <div class="sub">Execute command</div>
337
+ </div>
338
+ <div class="quick-btn" onclick="switchView('browser')">
339
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/></svg>
340
+ <div class="lbl">Browse</div>
341
+ <div class="sub">Web automation</div>
342
+ </div>
343
+ <div class="quick-btn" onclick="openModal('emailMod')">
344
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
345
+ <div class="lbl">Send Email</div>
346
+ <div class="sub">Compose message</div>
347
+ </div>
348
+ </div>
349
+ </div>
350
+ <!-- System Topology -->
351
+ <div class="home-card topo-card">
352
+ <h3><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg> System Topology</h3>
353
+ <div class="topo-vis">
354
+ <div class="topo-node center">LinguClaw</div>
355
+ <div class="topo-node" style="top:20%;left:20%">Chat</div>
356
+ <div class="topo-node" style="top:20%;right:20%">Browser</div>
357
+ <div class="topo-node" style="bottom:20%;left:20%">Memory</div>
358
+ <div class="topo-node" style="bottom:20%;right:20%">Scheduler</div>
359
+ <div class="topo-node" style="top:50%;left:10%">Tasks</div>
360
+ <div class="topo-node" style="top:50%;right:10%">Skills</div>
361
+ </div>
362
+ </div>
363
+ </div>
364
+ </div>
365
+ </div>
366
+ <!-- CHAT -->
367
+ <div class="view" id="v-chat">
368
+ <div class="chat-box" id="chatBox">
369
+ <div class="msg bot"><div class="msg-head"><span class="msg-who">LinguClaw</span></div><div class="msg-body">Hello. I'm your personal AI assistant. I can chat, send messages, browse the web, manage files, schedule jobs, and more. How can I help?</div></div>
370
+ </div>
371
+ <div class="typing" id="typing"><span>Processing</span><span class="dots"><span></span><span></span><span></span></span></div>
372
+ <div class="chat-bar">
373
+ <input class="inp" id="chatIn" placeholder="Ask me anything..." style="flex:1" onkeydown="if(event.key==='Enter'&&!event.shiftKey)sendChat()">
374
+ <button class="btn btn-p" id="chatBtn" onclick="sendChat()">Send</button>
375
+ </div>
376
+ </div>
377
+ <!-- TASKS -->
378
+ <div class="view" id="v-task">
379
+ <div class="panel-head"><h2>Task Runner</h2><p>Planner &rarr; Executor &rarr; Reviewer pipeline</p></div>
380
+ <div style="display:flex;gap:8px;padding:14px 20px;border-bottom:1px solid var(--border);background:var(--bg1)">
381
+ <input class="inp" id="taskIn" placeholder="Describe a task..." style="flex:1" onkeydown="if(event.key==='Enter')runTask()">
382
+ <button class="btn btn-p" id="taskBtn" onclick="runTask()">Run</button>
383
+ </div>
384
+ <div class="task-out" id="taskOut"><span style="color:var(--muted)">Type a task above and press Run.</span></div>
385
+ </div>
386
+ <!-- MEMORY -->
387
+ <div class="view" id="v-memory">
388
+ <div class="panel-head"><h2>Memory</h2><p id="memStats">Persistent memory store</p></div>
389
+ <div class="mem-list" id="memList"></div>
390
+ <div class="mem-bar">
391
+ <input class="inp" id="memKey" placeholder="Key" style="max-width:130px">
392
+ <input class="inp" id="memVal" placeholder="Value" style="flex:1">
393
+ <input class="inp" id="memCat" placeholder="Category" style="max-width:110px" value="general">
394
+ <button class="btn btn-p" onclick="addMem()">+ Add</button>
395
+ </div>
396
+ </div>
397
+ <!-- SKILLS -->
398
+ <div class="view" id="v-skills">
399
+ <div class="skills-head"><h2>Skills &amp; Integrations</h2><button class="btn btn-p btn-sm" onclick="openModal('skMod')">+ New Skill</button></div>
400
+ <div class="skills-body" id="skGrid"></div>
401
+ </div>
402
+ <!-- SCHEDULER -->
403
+ <div class="view" id="v-sched">
404
+ <div class="skills-head"><h2>Scheduler</h2><button class="btn btn-p btn-sm" onclick="openModal('jobMod')">+ New Job</button></div>
405
+ <div class="sched-body" id="jobList"></div>
406
+ </div>
407
+ <!-- BROWSER -->
408
+ <div class="view" id="v-browser">
409
+ <div class="brow-bar">
410
+ <div class="url-w"><svg class="ui" style="width:14px;height:14px;flex-shrink:0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg><input id="browUrl" placeholder="Enter URL or search query..." onkeydown="if(event.key==='Enter')browGo()"></div>
411
+ <button class="btn btn-p" onclick="browGo()">Go</button>
412
+ </div>
413
+ <div class="brow-tools" style="flex-wrap:wrap">
414
+ <button class="btn btn-g btn-sm" onclick="browSS()">Screenshot</button>
415
+ <button class="btn btn-g btn-sm" onclick="browSumm()">Summarize</button>
416
+ <button class="btn btn-g btn-sm" onclick="browSearch()">Search</button>
417
+ <span style="width:1px;height:20px;background:var(--border);margin:0 2px"></span>
418
+ <input class="inp" id="browAsk" placeholder="Ask about this page... or describe data to extract" style="flex:1;padding:5px 10px;font-size:11px" onkeydown="if(event.key==='Enter')browAskQ()">
419
+ <button class="btn btn-p btn-sm" onclick="browAskQ()">Ask AI</button>
420
+ <button class="btn btn-g btn-sm" onclick="browSmartEx()">Extract</button>
421
+ </div>
422
+ <div style="display:flex;gap:6px;padding:4px 20px;border-bottom:1px solid var(--border);background:var(--bg1)">
423
+ <button class="btn btn-g btn-sm" onclick="browEx()">CSS Select</button>
424
+ <input class="inp" id="browSel" placeholder="h1, .content, #main" style="flex:1;padding:4px 8px;font-size:11px">
425
+ </div>
426
+ <div class="brow-body" id="browOut"><div class="es"><p>Enter a URL to browse, or type a search query.<br>AI can summarize pages, answer questions, and extract data.</p>
427
+ <div style="display:flex;gap:6px;justify-content:center;margin-top:12px;flex-wrap:wrap">
428
+ <button class="btn btn-g btn-sm" onclick="document.getElementById('browUrl').value='https://news.ycombinator.com';browGo()">Hacker News</button>
429
+ <button class="btn btn-g btn-sm" onclick="document.getElementById('browUrl').value='https://github.com/trending';browGo()">GitHub Trending</button>
430
+ <button class="btn btn-g btn-sm" onclick="document.getElementById('browUrl').value='https://en.wikipedia.org/wiki/Special:Random';browGo()">Random Wiki</button>
431
+ </div>
432
+ </div></div>
433
+ </div>
434
+ <!-- INBOX -->
435
+ <div class="view" id="v-inbox">
436
+ <div class="panel-head"><h2>Inbox</h2><p>Incoming messages from email and messaging platforms</p></div>
437
+ <div style="display:flex;gap:8px;padding:14px 20px;border-bottom:1px solid var(--border);background:var(--bg1)">
438
+ <button class="btn btn-p" onclick="checkMail()">Check Mail</button>
439
+ <button class="btn btn-g" onclick="markAllRead()">Mark All Read</button>
440
+ <span style="flex:1"></span>
441
+ <span id="inboxStats" style="font-size:12px;color:var(--muted);align-self:center">0 unread</span>
442
+ </div>
443
+ <div class="mem-list" id="inboxList"></div>
444
+ </div>
445
+ <!-- WORKFLOWS -->
446
+ <div class="view" id="v-workflows">
447
+ <div class="skills-head"><h2><svg style="width:18px;height:18px;opacity:.7" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><path d="M6 21V9a9 9 0 0 0 9 9"/></svg> Workflows</h2><button class="btn btn-p btn-sm" onclick="openWorkflowEditor()">+ New Workflow</button></div>
448
+ <div style="padding:16px 20px;border-bottom:1px solid var(--border);background:var(--bg1);display:flex;gap:8px;align-items:center">
449
+ <input class="inp" id="wfSearch" placeholder="Search workflows..." style="flex:1;max-width:320px" oninput="filterWorkflows()">
450
+ <span style="flex:1"></span>
451
+ <span style="font-size:12px;color:var(--muted)" id="wfStats">0 workflows</span>
452
+ </div>
453
+ <div class="wf-list" id="wfList" style="flex:1;overflow-y:auto;padding:16px 20px"></div>
454
+ </div>
455
+ <!-- SETTINGS -->
456
+ <div class="view" id="v-settings">
457
+ <div class="panel-head"><h2>Settings</h2><p>LLM provider, model, and system configuration</p></div>
458
+ <div class="settings-body">
459
+ <div class="sc"><h3>LLM Provider</h3>
460
+ <div class="sr"><div class="sf"><label class="lbl">Provider</label><select class="inp" id="sP"><option value="openrouter">OpenRouter</option><option value="openai">OpenAI</option><option value="anthropic">Anthropic</option><option value="ollama">Ollama</option><option value="lmstudio">LM Studio</option></select></div><div class="sf"><label class="lbl">Model</label><input class="inp" id="sM" placeholder="openai/gpt-3.5-turbo"></div></div>
461
+ <div class="sf"><label class="lbl">API Key</label><input class="inp" type="password" id="sK" placeholder="Enter new API key..."><div class="sh">Leave empty to keep current key.</div></div>
462
+ <div class="sr"><div class="sf"><label class="lbl">Max Tokens</label><input class="inp" type="number" id="sMT" min="100" max="8000"></div><div class="sf"><label class="lbl">Temperature</label><input class="inp" type="number" id="sT" min="0" max="2" step="0.1"></div></div>
463
+ </div>
464
+ <div class="sc"><h3>System</h3>
465
+ <div class="sr"><div class="sf"><label class="lbl">Max Steps</label><input class="inp" type="number" id="sSt" min="1" max="50"></div><div class="sf"><label class="lbl">Log Level</label><select class="inp" id="sL"><option value="debug">Debug</option><option value="info">Info</option><option value="warn">Warning</option><option value="error">Error</option></select></div></div>
466
+ <div class="sf"><label class="lbl">Safety Mode</label><select class="inp" id="sSf"><option value="strict">Strict</option><option value="balanced">Balanced</option><option value="permissive">Permissive</option></select></div>
467
+ <div class="sa"><button class="btn btn-p" onclick="saveSett()">Save</button><button class="btn btn-g" onclick="resetSett()">Reset</button></div>
468
+ </div>
469
+ </div>
470
+ </div>
471
+ </div>
472
+ </div>
473
+ <!-- SKILL MODAL -->
474
+ <div class="modal-bg" id="skMod"><div class="modal"><h3>Add New Skill</h3>
475
+ <div class="fld"><label class="lbl">Skill Name</label><input class="inp" id="skN" placeholder="e.g. weather"></div>
476
+ <div class="fld"><label class="lbl">Description</label><input class="inp" id="skD" placeholder="Fetch weather information"></div>
477
+ <div class="fld"><label class="lbl">Type</label><select class="inp" id="skT"><option value="plugin">Plugin</option><option value="api">API</option><option value="shell">Shell</option><option value="javascript">JavaScript</option></select></div>
478
+ <div class="fld"><label class="lbl">Command / Endpoint</label><input class="inp" id="skC" placeholder="curl wttr.in/London"></div>
479
+ <div class="acts"><button class="btn btn-g" onclick="closeModal('skMod')">Cancel</button><button class="btn btn-p" onclick="addSk()">Add</button></div>
480
+ </div></div>
481
+ <!-- JOB MODAL -->
482
+ <div class="modal-bg" id="jobMod"><div class="modal"><h3>New Scheduled Job</h3>
483
+ <div class="fld"><label class="lbl">Job Name</label><input class="inp" id="jN" placeholder="e.g. Backup"></div>
484
+ <div class="fld"><label class="lbl">Type</label><select class="inp" id="jT"><option value="interval">Interval</option><option value="cron">Cron</option><option value="once">One-time</option><option value="reminder">Reminder</option></select></div>
485
+ <div class="fld"><label class="lbl">Schedule</label><input class="inp" id="jS" placeholder="30m, 1h, */5 * * * *"><div class="sh">Interval: 30s,5m,1h,1d | Cron: */5 * * * *</div></div>
486
+ <div class="fld"><label class="lbl">Command</label><input class="inp" id="jC" placeholder="ls -la /tmp"></div>
487
+ <div class="acts"><button class="btn btn-g" onclick="closeModal('jobMod')">Cancel</button><button class="btn btn-p" onclick="addJob()">Create</button></div>
488
+ </div></div>
489
+ <!-- INTEGRATION CONFIG MODAL -->
490
+ <div class="modal-bg" id="intMod"><div class="modal"><h3 id="intModTitle">Configure Integration</h3>
491
+ <div id="intFields"></div>
492
+ <div class="acts"><button class="btn btn-g" onclick="closeModal('intMod')">Cancel</button><button class="btn btn-d btn-sm" onclick="delIntCfg()" id="intDelBtn" style="display:none">Remove</button><button class="btn btn-p" onclick="saveIntCfg()">Save</button></div>
493
+ </div></div>
494
+ <!-- EMAIL COMPOSE MODAL -->
495
+ <div class="modal-bg" id="emailMod"><div class="modal"><h3>Compose Email</h3>
496
+ <div class="fld"><label class="lbl">To</label><input class="inp" id="emTo" placeholder="recipient@example.com"></div>
497
+ <div class="fld"><label class="lbl">Subject</label><input class="inp" id="emSubj" placeholder="Email subject"></div>
498
+ <div class="fld"><label class="lbl">Message</label><textarea class="inp" id="emBody" rows="6" placeholder="Write your message here..." style="resize:vertical;font-family:inherit"></textarea></div>
499
+ <div id="emStatus" style="display:none;padding:8px 12px;border-radius:8px;font-size:12px;margin-bottom:10px"></div>
500
+ <div class="acts"><button class="btn btn-g" onclick="closeModal('emailMod')">Cancel</button><button class="btn btn-p" id="emSendBtn" onclick="sendEmail()">Send</button></div>
501
+ </div></div>
502
+ <script>
503
+ function esc(s){if(!s)return'';var d=document.createElement('div');d.textContent=String(s);return d.innerHTML}
504
+ function toast(m,t){var e=document.getElementById('toast');e.textContent=m;e.className='toast show '+(t||'info');clearTimeout(e._t);e._t=setTimeout(function(){e.className='toast'},3000)}
505
+ function openModal(id){document.getElementById(id).classList.add('open')}
506
+ function closeModal(id){document.getElementById(id).classList.remove('open')}
507
+ function switchView(v){
508
+ document.querySelectorAll('.ni').forEach(function(x){x.classList.remove('a')});
509
+ document.querySelector('.ni[data-v="'+v+'"]').classList.add('a');
510
+ document.querySelectorAll('.view').forEach(function(x){x.classList.remove('active')});
511
+ document.getElementById('v-'+v).classList.add('active');
512
+ if(v==='memory')loadMem();if(v==='skills')loadSk();if(v==='sched')loadJobs();if(v==='settings')loadSett();if(v==='inbox')loadInbox();if(v==='workflows')loadWorkflows();
513
+ }
514
+ // NAV
515
+ document.querySelectorAll('.ni[data-v]').forEach(function(n){n.addEventListener('click',function(){
516
+ switchView(n.dataset.v);
517
+ })});
518
+ // CHAT
519
+ var cBusy=0;
520
+ function sendChat(){var i=document.getElementById('chatIn'),m=i.value.trim();if(!m||cBusy)return;cBusy=1;i.value='';document.getElementById('chatBtn').disabled=true;
521
+ addMsg('user',m);document.getElementById('typing').classList.add('show');
522
+ // Use streaming endpoint
523
+ fetch('/api/chat/stream',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:m})})
524
+ .then(function(r){
525
+ if(!r.ok){return r.json().then(function(d){throw new Error(d.error||'Request failed')})}
526
+ // Create bot message element for streaming
527
+ var b=document.getElementById('chatBox'),d=document.createElement('div');d.className='msg bot';
528
+ var now=new Date(),ts=now.getHours().toString().padStart(2,'0')+':'+now.getMinutes().toString().padStart(2,'0');
529
+ d.innerHTML='<div class="msg-head"><span class="msg-who">LinguClaw</span><span class="msg-model" id="streamModel"></span><span class="msg-time">'+ts+'</span></div><div class="msg-body" id="streamBody"></div>';
530
+ b.appendChild(d);
531
+ document.getElementById('typing').classList.remove('show');
532
+ var reader=r.body.getReader(),decoder=new TextDecoder(),fullText='',buf='';
533
+ function readChunk(){
534
+ reader.read().then(function(result){
535
+ if(result.done){cBusy=0;document.getElementById('chatBtn').disabled=false;return}
536
+ buf+=decoder.decode(result.value,{stream:true});
537
+ var lines=buf.split('\n');buf=lines.pop()||'';
538
+ for(var li=0;li<lines.length;li++){
539
+ var line=lines[li].trim();if(!line.startsWith('data: '))continue;
540
+ try{var ev=JSON.parse(line.slice(6));
541
+ if(ev.done){var sm=document.getElementById('streamModel');if(sm&&ev.model)sm.textContent=ev.model;
542
+ document.getElementById('streamBody').removeAttribute('id');document.getElementById('streamModel').removeAttribute('id');
543
+ cBusy=0;document.getElementById('chatBtn').disabled=false;return}
544
+ if(ev.error){addMsg('err',ev.error);cBusy=0;document.getElementById('chatBtn').disabled=false;return}
545
+ if(ev.chunk){fullText+=ev.chunk;var el=document.getElementById('streamBody');if(el){el.textContent=fullText;b.scrollTop=b.scrollHeight}}
546
+ }catch(e){}
547
+ }
548
+ readChunk();
549
+ }).catch(function(e){cBusy=0;document.getElementById('chatBtn').disabled=false;addMsg('err','Stream error: '+e.message)});
550
+ }
551
+ readChunk();
552
+ }).catch(function(e){document.getElementById('typing').classList.remove('show');cBusy=0;document.getElementById('chatBtn').disabled=false;addMsg('err','Connection error: '+e.message)});}
553
+ function addMsg(t,tx,mod){var b=document.getElementById('chatBox'),d=document.createElement('div');d.className='msg '+t;
554
+ var now=new Date(),ts=now.getHours().toString().padStart(2,'0')+':'+now.getMinutes().toString().padStart(2,'0');
555
+ if(t==='user'){
556
+ d.innerHTML='<div class="msg-head"><span class="msg-who">You</span><span class="msg-time">'+ts+'</span></div><div class="msg-body">'+esc(tx)+'</div>';
557
+ }else if(t==='bot'){
558
+ d.innerHTML='<div class="msg-head"><span class="msg-who">LinguClaw</span>'+(mod?'<span class="msg-model">'+esc(mod)+'</span>':'')+'<span class="msg-time">'+ts+'</span></div><div class="msg-body">'+esc(tx)+'</div>';
559
+ }else if(t==='action'){
560
+ var isOk=tx.indexOf('✅')>=0;
561
+ d.innerHTML='<div class="msg-head"><span class="msg-who">LinguClaw</span><span class="msg-badge '+(isOk?'ok':'fail')+'">'+(isOk?'Executed':'Failed')+'</span>'+(mod?'<span class="msg-model">'+esc(mod)+'</span>':'')+'<span class="msg-time">'+ts+'</span></div><div class="msg-body">'+esc(tx)+'</div>';
562
+ }else if(t==='err'){
563
+ d.innerHTML='<div class="msg-head"><span class="msg-who">Error</span><span class="msg-time">'+ts+'</span></div><div class="msg-body">'+esc(tx)+'</div>';
564
+ }else if(t==='info'){
565
+ d.innerHTML='<div class="msg-head"><span class="msg-who">Info</span><span class="msg-time">'+ts+'</span></div><div class="msg-body">'+esc(tx)+'</div>';
566
+ }
567
+ b.appendChild(d);b.scrollTop=b.scrollHeight}
568
+ // TASKS
569
+ var tBusy=0;
570
+ function runTask(){var i=document.getElementById('taskIn'),t=i.value.trim();if(!t||tBusy)return;tBusy=1;document.getElementById('taskBtn').disabled=true;
571
+ var o=document.getElementById('taskOut');o.innerHTML='<span style="color:var(--accent3)">Task: '+esc(t)+'</span>\n\n';
572
+ fetch('/api/task',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({task:t,max_steps:15})})
573
+ .then(function(r){return r.json()}).then(function(d){if(d.error){o.innerHTML+='<span style="color:var(--red)">Error: '+esc(d.error)+'</span>\n';tBusy=0;document.getElementById('taskBtn').disabled=false}
574
+ else o.innerHTML+='<span style="color:var(--green)">Queued (ID: '+d.task_id+')</span>\n'})
575
+ .catch(function(e){o.innerHTML+='<span style="color:var(--red)">Error: '+esc(e.message)+'</span>\n';tBusy=0;document.getElementById('taskBtn').disabled=false})}
576
+ // MEMORY
577
+ function loadMem(){fetch('/api/memory').then(function(r){return r.json()}).then(function(d){
578
+ var en=d.entries||d||[],st=d.stats||{},tot=st.total_entries||en.length;
579
+ document.getElementById('memStats').innerHTML='Persistent memory &mdash; <strong>'+tot+'</strong> entries';document.getElementById('cntMem').textContent=tot;
580
+ var h='';if(!en.length)h='<div class="es"><p>No memories stored yet.</p></div>';
581
+ for(var i=0;i<en.length;i++){var e=en[i];h+='<div class="mi2"><span class="mk">'+esc(e.key)+'</span><span class="mv">'+esc(typeof e.value==='string'?e.value:JSON.stringify(e.value))+'</span><span class="mc">'+esc(e.category||'general')+'</span><button class="btn btn-d btn-sm" onclick="delMem(\''+esc(e.key)+'\')">Delete</button></div>'}
582
+ document.getElementById('memList').innerHTML=h}).catch(function(){document.getElementById('memList').innerHTML='<div class="es"><p>Failed to load.</p></div>'})}
583
+ function addMem(){var k=document.getElementById('memKey').value.trim(),v=document.getElementById('memVal').value.trim(),c=document.getElementById('memCat').value.trim()||'general';
584
+ if(!k||!v){toast('Key and value required','err');return}
585
+ fetch('/api/memory',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({key:k,value:v,category:c})})
586
+ .then(function(){document.getElementById('memKey').value='';document.getElementById('memVal').value='';toast('Saved','ok');loadMem()})}
587
+ function delMem(k){fetch('/api/memory/'+encodeURIComponent(k),{method:'DELETE'}).then(function(){toast('Deleted','ok');loadMem()})}
588
+ // SKILLS
589
+ var cSk=JSON.parse(localStorage.getItem('lc_skills')||'[]');
590
+ var _skData=[];
591
+ function loadSk(){fetch('/api/skills').then(function(r){return r.json()}).then(function(sk){
592
+ _skData=sk;var all=sk.concat(cSk);document.getElementById('cntSk').textContent=all.length;
593
+ var svI='<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">';
594
+ var ic={shell:svI+'<polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>',filesystem:svI+'<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>',browser:svI+'<circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/></svg>',email:svI+'<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>',scheduler:svI+'<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>',memory:svI+'<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>',telegram:svI+'<line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>',discord:svI+'<path d="M18 8a3 3 0 0 0-3-3H9a3 3 0 0 0-3 3v8a3 3 0 0 0 3 3h6a3 3 0 0 0 3-3z"/><circle cx="10" cy="12" r="1"/><circle cx="14" cy="12" r="1"/></svg>',slack:svI+'<rect x="13" y="2" width="3" height="8" rx="1.5"/><rect x="8" y="14" width="3" height="8" rx="1.5"/><rect x="2" y="8" width="8" height="3" rx="1.5"/><rect x="14" y="13" width="8" height="3" rx="1.5"/></svg>',whatsapp:svI+'<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/></svg>'};
595
+ var h='';for(var i=0;i<all.length;i++){var s=all[i],tp=s.type||'plugin',on=s.enabled!==false,isC=s._custom,hasCfg=s.configFields&&s.configFields.length>0;
596
+ var isEmail=s.name==='email'&&on;
597
+ h+='<div class="sk"><div class="sk-top"><div class="sk-ic">'+(ic[s.name]||ic['shell'])+'</div><div><div class="sk-name">'+esc(s.name)+'</div><div class="sk-type">'+esc(tp)+'</div></div></div><div class="sk-desc">'+esc(s.description)+'</div><div class="sk-foot"><div class="sk-st '+(on?'on':'off')+'"><span class="sd"></span> '+(on?'Active':'Inactive')+'</div><div style="display:flex;gap:4px">'+(isEmail?'<button class="btn btn-g btn-sm" onclick="openModal(\'emailMod\')">Send</button>':'')+(hasCfg?'<button class="btn btn-p btn-sm" onclick="openIntCfg(\''+esc(s.name)+'\')">Configure</button>':'')+(isC?'<button class="btn btn-d btn-sm" onclick="delSk('+i+','+sk.length+')">Delete</button>':'')+'</div></div>'+(isC?'<button class="btn btn-d btn-sm sk-del" onclick="delSk('+i+','+sk.length+')">x</button>':'')+'</div>'}
598
+ document.getElementById('skGrid').innerHTML=h||'<div class="es"><p>No skills available.</p></div>'})}
599
+ function openAddSk(){openModal('skMod')}
600
+ function addSk(){var n=document.getElementById('skN').value.trim(),d=document.getElementById('skD').value.trim(),t=document.getElementById('skT').value,c=document.getElementById('skC').value.trim();
601
+ if(!n){toast('Skill name required','err');return}
602
+ cSk.push({name:n,description:d||n,type:t,enabled:true,command:c,_custom:true});
603
+ localStorage.setItem('lc_skills',JSON.stringify(cSk));closeModal('skMod');
604
+ document.getElementById('skN').value='';document.getElementById('skD').value='';document.getElementById('skC').value='';
605
+ toast(n+' added','ok');loadSk()}
606
+ function delSk(idx,srvLen){var ci=idx-srvLen;if(ci>=0&&ci<cSk.length){var nm=cSk[ci].name;cSk.splice(ci,1);localStorage.setItem('lc_skills',JSON.stringify(cSk));toast(nm+' removed','ok');loadSk()}}
607
+ // INTEGRATION CONFIG
608
+ var _cfgName='';
609
+ function openIntCfg(name){_cfgName=name;
610
+ var sk=_skData.find(function(s){return s.name===name});if(!sk||!sk.configFields)return;
611
+ document.getElementById('intModTitle').textContent='Configure '+name.charAt(0).toUpperCase()+name.slice(1);
612
+ fetch('/api/skills/config/'+encodeURIComponent(name)).then(function(r){return r.json()}).then(function(d){
613
+ var cfg=d.config||{};document.getElementById('intDelBtn').style.display=d.hasConfig?'inline-flex':'none';
614
+ var h='';for(var i=0;i<sk.configFields.length;i++){var f=sk.configFields[i];
615
+ h+='<div class="fld"><label class="lbl">'+esc(f.label)+'</label><input class="inp" id="int_'+esc(f.key)+'" type="'+(f.secret?'password':'text')+'" placeholder="'+esc(f.placeholder||'')+'" value="'+esc(cfg[f.key]||'')+'"></div>'}
616
+ document.getElementById('intFields').innerHTML=h;openModal('intMod')})}
617
+ function saveIntCfg(){var sk=_skData.find(function(s){return s.name===_cfgName});if(!sk)return;
618
+ var cfg={};for(var i=0;i<sk.configFields.length;i++){var f=sk.configFields[i];var el=document.getElementById('int_'+f.key);if(el)cfg[f.key]=el.value}
619
+ fetch('/api/skills/config/'+encodeURIComponent(_cfgName),{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({config:cfg})})
620
+ .then(function(r){return r.json()}).then(function(d){if(d.success){toast(_cfgName+' configured','ok');closeModal('intMod');loadSk()}else toast(d.error||'Error','err')})}
621
+ function delIntCfg(){if(!confirm('Remove '+_cfgName+' configuration?'))return;
622
+ fetch('/api/skills/config/'+encodeURIComponent(_cfgName),{method:'DELETE'}).then(function(){toast(_cfgName+' config removed','ok');closeModal('intMod');loadSk()})}
623
+ // EMAIL SEND
624
+ function sendEmail(){var to=document.getElementById('emTo').value.trim(),subj=document.getElementById('emSubj').value.trim(),body=document.getElementById('emBody').value;
625
+ if(!to||!subj){toast('To and Subject required','err');return}
626
+ var st=document.getElementById('emStatus');st.style.display='block';st.style.background='rgba(37,99,235,.1)';st.style.color='var(--accent)';st.textContent='Sending...';
627
+ document.getElementById('emSendBtn').disabled=true;
628
+ fetch('/api/email/send',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({to:to,subject:subj,body:body})})
629
+ .then(function(r){return r.json()}).then(function(d){
630
+ document.getElementById('emSendBtn').disabled=false;
631
+ if(d.success){st.style.background='rgba(22,163,74,.1)';st.style.color='#16a34a';st.textContent=d.message;
632
+ document.getElementById('emTo').value='';document.getElementById('emSubj').value='';document.getElementById('emBody').value='';
633
+ toast('Email sent!','ok');setTimeout(function(){closeModal('emailMod');st.style.display='none'},2000)}
634
+ else{st.style.background='rgba(239,68,68,.1)';st.style.color='var(--red)';st.textContent=d.error}
635
+ }).catch(function(e){document.getElementById('emSendBtn').disabled=false;st.style.background='rgba(239,68,68,.1)';st.style.color='var(--red)';st.textContent=e.message})}
636
+ function testEmail(){toast('Sending test email...','info');
637
+ fetch('/api/skills/test/email',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({})})
638
+ .then(function(r){return r.json()}).then(function(d){
639
+ if(d.success)toast(d.message,'ok');else toast(d.error||'Failed','err')
640
+ }).catch(function(e){toast(e.message,'err')})}
641
+ // SCHEDULER
642
+ function loadJobs(){fetch('/api/scheduler/jobs').then(function(r){return r.json()}).then(function(jobs){
643
+ document.getElementById('cntJob').textContent=jobs.length;
644
+ if(!jobs.length){document.getElementById('jobList').innerHTML='<div class="es"><p>No scheduled jobs.</p><button class="btn btn-p" onclick="openModal(\'jobMod\')">+ Create job</button></div>';return}
645
+ var clkSvg='<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>';
646
+ var h='';for(var i=0;i<jobs.length;i++){var j=jobs[i];
647
+ h+='<div class="job"><div class="job-ic">'+clkSvg+'</div><div class="job-info"><div class="job-name">'+esc(j.name)+'</div><div class="job-meta"><span>'+esc(j.type)+'</span><span>'+esc(j.schedule)+'</span><span>'+(j.enabled?'Active':'Paused')+'</span>'+(j.runCount?'<span>'+j.runCount+'x</span>':'')+'</div><div class="job-cmd">'+esc(j.command)+'</div></div><div class="job-acts"><button class="btn btn-g btn-sm" onclick="togJob(\''+j.id+'\')">'+(j.enabled?'Pause':'Resume')+'</button><button class="btn btn-d btn-sm" onclick="delJob(\''+j.id+'\')">Delete</button></div></div>'}
648
+ document.getElementById('jobList').innerHTML=h})}
649
+ function openAddJob(){openModal('jobMod')}
650
+ function addJob(){var n=document.getElementById('jN').value.trim(),t=document.getElementById('jT').value,s=document.getElementById('jS').value.trim(),c=document.getElementById('jC').value.trim();
651
+ if(!n||!s||!c){toast('All fields required','err');return}
652
+ fetch('/api/scheduler/jobs',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name:n,type:t,schedule:s,command:c})})
653
+ .then(function(){closeModal('jobMod');document.getElementById('jN').value='';document.getElementById('jS').value='';document.getElementById('jC').value='';toast(n+' created','ok');loadJobs()})}
654
+ function togJob(id){fetch('/api/scheduler/jobs/'+id+'/toggle',{method:'POST'}).then(function(){loadJobs()})}
655
+ function delJob(id){fetch('/api/scheduler/jobs/'+id,{method:'DELETE'}).then(function(){toast('Deleted','ok');loadJobs()})}
656
+ // BROWSER
657
+ var _browLoaded=false;
658
+ function browLoading(msg){document.getElementById('browOut').innerHTML='<div style="color:var(--muted);padding:40px;text-align:center"><svg style="width:20px;height:20px;animation:spin 1s linear infinite;margin-bottom:8px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg><br>'+esc(msg||'Loading...')+'</div>'}
659
+ function browGo(ss){var u=document.getElementById('browUrl').value.trim();if(!u)return;
660
+ browLoading(ss?'Taking screenshot...':'Loading page...');
661
+ var ep=ss?'/api/browser/screenshot':'/api/browser/browse';
662
+ fetch(ep,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url:u})})
663
+ .then(function(r){return r.json()}).then(function(d){
664
+ if(!d.success){document.getElementById('browOut').innerHTML='<div style="color:var(--red);padding:20px">'+esc(d.error||'Error')+'</div>';return}
665
+ _browLoaded=true;var h='<div class="brow-t">'+esc(d.title||'')+'</div><div class="brow-u">'+esc(d.url)+'</div>';
666
+ if(d.screenshot)h+='<img src="data:image/png;base64,'+d.screenshot+'" style="max-width:100%;border-radius:8px;border:1px solid var(--border);margin-bottom:12px">';
667
+ if(d.content)h+='<div class="brow-c">'+esc(d.content)+'</div>';
668
+ if(d.links&&d.links.length){h+='<div class="brow-l"><h4>Links ('+d.links.length+')</h4>';
669
+ for(var i=0;i<Math.min(d.links.length,30);i++)h+='<a onclick="document.getElementById(\'browUrl\').value=\''+esc(d.links[i].href).replace(/'/g,"\\'")+'\';browGo();return false">'+esc(d.links[i].text||d.links[i].href)+'</a>';h+='</div>'}
670
+ document.getElementById('browOut').innerHTML=h}).catch(function(e){document.getElementById('browOut').innerHTML='<div style="color:var(--red);padding:20px">'+esc(e.message)+'</div>'})}
671
+ function browSS(){browGo(true)}
672
+ function browEx(){var s=document.getElementById('browSel').value.trim();if(!s){toast('Enter a CSS selector','err');return}
673
+ browLoading('Extracting by CSS: '+s);
674
+ fetch('/api/browser/extract',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({selector:s})})
675
+ .then(function(r){return r.json()}).then(function(d){
676
+ if(!d.success){toast(d.error||'Error','err');return}
677
+ document.getElementById('browOut').innerHTML='<div class="brow-t">CSS Extract: '+esc(s)+'</div><div class="brow-c">'+esc(JSON.stringify(d.data,null,2))+'</div>'})
678
+ .catch(function(e){toast(e.message,'err')})}
679
+ function browSumm(){if(!_browLoaded){toast('Browse a page first','err');return}
680
+ browLoading('AI is summarizing the page...');
681
+ fetch('/api/browser/summarize',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({})})
682
+ .then(function(r){return r.json()}).then(function(d){
683
+ if(d.error){document.getElementById('browOut').innerHTML='<div style="color:var(--red);padding:20px">'+esc(d.error)+'</div>';return}
684
+ var h='<div class="brow-t">AI Summary</div>';
685
+ if(d.title)h+='<div class="brow-u">'+esc(d.title)+' &mdash; <span style="color:var(--muted);font-size:10px">'+esc(d.model||'')+'</span></div>';
686
+ h+='<div style="font-size:13px;line-height:1.7;color:var(--text);white-space:pre-wrap;background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:16px">'+esc(d.summary)+'</div>';
687
+ document.getElementById('browOut').innerHTML=h}).catch(function(e){toast(e.message,'err')})}
688
+ function browAskQ(){var q=document.getElementById('browAsk').value.trim();if(!q){toast('Type a question first','err');return}
689
+ if(!_browLoaded){toast('Browse a page first','err');return}
690
+ browLoading('AI is analyzing the page...');
691
+ fetch('/api/browser/ask',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({question:q})})
692
+ .then(function(r){return r.json()}).then(function(d){
693
+ if(d.error){document.getElementById('browOut').innerHTML='<div style="color:var(--red);padding:20px">'+esc(d.error)+'</div>';return}
694
+ var h='<div class="brow-t">Q&A — '+esc(d.title||'page')+'</div>';
695
+ h+='<div style="margin:10px 0;padding:10px 14px;background:rgba(37,99,235,.1);border:1px solid rgba(37,99,235,.2);border-radius:8px;font-size:13px;color:var(--accent3)"><strong>Q:</strong> '+esc(d.question)+'</div>';
696
+ h+='<div style="font-size:13px;line-height:1.7;color:var(--text);white-space:pre-wrap;background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:16px">'+esc(d.answer)+'</div>';
697
+ h+='<div style="font-size:10px;color:var(--muted);margin-top:6px">'+esc(d.model||'')+'</div>';
698
+ document.getElementById('browOut').innerHTML=h;document.getElementById('browAsk').value=''}).catch(function(e){toast(e.message,'err')})}
699
+ function browSmartEx(){var q=document.getElementById('browAsk').value.trim();if(!q){toast('Describe what to extract (e.g. "all prices", "email addresses", "product names")','err');return}
700
+ if(!_browLoaded){toast('Browse a page first','err');return}
701
+ browLoading('AI is extracting: '+q+'...');
702
+ fetch('/api/browser/smart-extract',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({prompt:q})})
703
+ .then(function(r){return r.json()}).then(function(d){
704
+ if(d.error){document.getElementById('browOut').innerHTML='<div style="color:var(--red);padding:20px">'+esc(d.error)+'</div>';return}
705
+ var h='<div class="brow-t">Extracted: '+esc(d.prompt)+'</div>';
706
+ if(d.title)h+='<div class="brow-u">from '+esc(d.title)+'</div>';
707
+ h+='<div style="font-size:13px;line-height:1.7;color:var(--text);white-space:pre-wrap;background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:16px;font-family:monospace">'+esc(d.extracted)+'</div>';
708
+ h+='<div style="font-size:10px;color:var(--muted);margin-top:6px">'+esc(d.model||'')+'</div>';
709
+ document.getElementById('browOut').innerHTML=h;document.getElementById('browAsk').value=''}).catch(function(e){toast(e.message,'err')})}
710
+ function browSearch(){var q=document.getElementById('browUrl').value.trim();if(!q){toast('Type a search query in the URL bar','err');return}
711
+ browLoading('Searching: '+q+'...');
712
+ fetch('/api/browser/search',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({query:q})})
713
+ .then(function(r){return r.json()}).then(function(d){
714
+ if(d.error){document.getElementById('browOut').innerHTML='<div style="color:var(--red);padding:20px">'+esc(d.error)+'</div>';return}
715
+ _browLoaded=true;var h='<div class="brow-t">Search: '+esc(d.query)+'</div>';
716
+ if(d.answer)h+='<div style="font-size:13px;line-height:1.7;color:var(--text);white-space:pre-wrap;background:var(--bg2);border:1px solid rgba(37,99,235,.2);border-radius:10px;padding:16px;margin:12px 0">'+esc(d.answer)+'</div>';
717
+ if(d.model)h+='<div style="font-size:10px;color:var(--muted);margin-bottom:12px">'+esc(d.model)+'</div>';
718
+ if(d.links&&d.links.length){h+='<div class="brow-l"><h4>Sources ('+d.links.length+')</h4>';
719
+ for(var i=0;i<Math.min(d.links.length,15);i++)h+='<a onclick="document.getElementById(\'browUrl\').value=\''+esc(d.links[i].href).replace(/'/g,"\\'")+'\';browGo();return false">'+esc(d.links[i].text||d.links[i].href)+'</a>';h+='</div>'}
720
+ document.getElementById('browOut').innerHTML=h}).catch(function(e){document.getElementById('browOut').innerHTML='<div style="color:var(--red);padding:20px">'+esc(e.message)+'</div>'})}
721
+ // SETTINGS
722
+ function loadSett(){fetch('/api/settings').then(function(r){return r.json()}).then(function(s){
723
+ document.getElementById('sP').value=s.llm.provider||'';document.getElementById('sM').value=s.llm.model||'';
724
+ document.getElementById('sMT').value=s.llm.maxTokens||1000;document.getElementById('sT').value=s.llm.temperature||0.7;
725
+ document.getElementById('sSt').value=s.system.maxSteps||15;document.getElementById('sL').value=s.system.logLevel||'info';
726
+ document.getElementById('sSf').value=s.system.safetyMode||'balanced';document.getElementById('modelBadge').textContent=s.llm.model||'?'})}
727
+ function saveSett(){var d={llm:{provider:document.getElementById('sP').value,model:document.getElementById('sM').value,maxTokens:parseInt(document.getElementById('sMT').value),temperature:parseFloat(document.getElementById('sT').value)},system:{maxSteps:parseInt(document.getElementById('sSt').value),logLevel:document.getElementById('sL').value,safetyMode:document.getElementById('sSf').value}};
728
+ var k=document.getElementById('sK').value;if(k)d.llm.apiKey=k;
729
+ fetch('/api/settings',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(d)})
730
+ .then(function(r){if(r.ok){toast('Settings saved','ok');document.getElementById('sK').value='';document.getElementById('modelBadge').textContent=d.llm.model}else toast('Save failed','err')})}
731
+ function resetSett(){if(!confirm('Reset all settings?'))return;
732
+ fetch('/api/settings',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({llm:{provider:'openrouter',model:'openai/gpt-3.5-turbo',maxTokens:1000,temperature:0.7},system:{maxSteps:15,logLevel:'info',safetyMode:'balanced'}})})
733
+ .then(function(){loadSett();toast('Reset complete','ok')})}
734
+ // INBOX
735
+ function loadInbox(){
736
+ fetch('/api/inbox/messages?limit=50').then(function(r){return r.json()}).then(function(d){
737
+ var msgs=d.messages||[],unread=d.unread||0;
738
+ document.getElementById('cntInbox').textContent=unread;document.getElementById('cntInbox').style.display=unread?'inline':'none';
739
+ document.getElementById('inboxStats').textContent=unread+' unread';
740
+ var h='';
741
+ if(!msgs.length){h='<div class="es"><p>No messages yet.</p><button class="btn btn-p" onclick="checkMail()">Check Mail Now</button></div>';}
742
+ for(var i=0;i<msgs.length;i++){var m=msgs[i];
743
+ h+='<div class="mi2" style="'+(m.read?'opacity:.7':'border-left:3px solid var(--accent)')+'">';
744
+ h+='<div style="flex:1;min-width:0">';
745
+ h+='<div style="display:flex;align-items:center;gap:8px;margin-bottom:4px">';
746
+ h+='<span style="font-size:10px;padding:2px 6px;background:'+(m.platform==='email'?'var(--accent)':m.platform==='telegram'?'#0088cc':m.platform==='discord'?'#7289da':'var(--bg3)')+';color:#fff;border-radius:4px;font-weight:600">'+esc(m.platform)+'</span>';
747
+ h+='<span style="font-weight:600;font-size:13px">'+esc(m.from)+'</span>';
748
+ h+='<span style="margin-left:auto;font-size:11px;color:var(--muted)">'+esc(m.timestamp?new Date(m.timestamp).toLocaleString():'')+'</span>';
749
+ h+='</div>';
750
+ h+='<div style="font-weight:500;margin-bottom:2px">'+esc(m.subject||'(no subject)')+'</div>';
751
+ h+='<div style="font-size:12px;color:var(--dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">'+esc(m.body||'')+'</div>';
752
+ h+='</div>';
753
+ h+='<div style="display:flex;flex-direction:column;gap:4px;margin-left:8px">';
754
+ if(!m.read)h+='<button class="btn btn-g btn-sm" onclick="markRead(\''+m.id+'\')">Mark Read</button>';
755
+ h+='<button class="btn btn-d btn-sm" onclick="delMsg(\''+m.id+'\')">Delete</button>';
756
+ h+='</div>';
757
+ h+='</div>';
758
+ }
759
+ document.getElementById('inboxList').innerHTML=h;
760
+ }).catch(function(){document.getElementById('inboxList').innerHTML='<div class="es"><p>Failed to load inbox.</p></div>';});}
761
+ function checkMail(){toast('Checking for new emails...','info');fetch('/api/inbox/check',{method:'POST'}).then(function(r){return r.json()}).then(function(d){if(d.checked){toast('Checked '+d.checked+' folders','ok');loadInbox()}else toast(d.error||'Check failed','err')}).catch(function(e){toast(e.message,'err')});}
762
+ function markRead(id){fetch('/api/inbox/messages/'+id+'/read',{method:'POST'}).then(function(){loadInbox();});}
763
+ function markAllRead(){fetch('/api/inbox/mark-all-read',{method:'POST'}).then(function(){toast('All messages marked read','ok');loadInbox();});}
764
+ function delMsg(id){fetch('/api/inbox/messages/'+id,{method:'DELETE'}).then(function(){loadInbox();});}
765
+ // WORKFLOWS
766
+ var _wfData=[];
767
+ function loadWorkflows(){
768
+ fetch('/api/workflows').then(function(r){return r.json()}).then(function(wfs){
769
+ _wfData=wfs||[];document.getElementById('cntWf').textContent=_wfData.length;
770
+ document.getElementById('wfStats').textContent=_wfData.length+' workflow'+(_wfData.length===1?'':'s');
771
+ renderWorkflows(_wfData);
772
+ }).catch(function(){document.getElementById('wfList').innerHTML='<div class="es"><p>Failed to load workflows.</p></div>'})
773
+ }
774
+ function renderWorkflows(wfs){
775
+ if(!wfs.length){document.getElementById('wfList').innerHTML='<div class="es"><svg style="width:48px;height:48px;color:var(--muted);opacity:.3;margin-bottom:12px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><path d="M6 21V9a9 9 0 0 0 9 9"/></svg><p>No workflows yet</p><button class="btn btn-p" onclick="openWorkflowEditor()">Create your first workflow</button></div>';return}
776
+ var h='';for(var i=0;i<wfs.length;i++){var w=wfs[i];
777
+ h+='<div class="wf-item" onclick="openWorkflowEditor(\''+esc(w.id)+'\')">';
778
+ h+='<div class="wf-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><path d="M6 21V9a9 9 0 0 0 9 9"/></svg></div>';
779
+ h+='<div class="wf-info"><div class="wf-name">'+esc(w.name)+'</div>';
780
+ if(w.description)h+='<div class="wf-desc">'+esc(w.description)+'</div>';
781
+ h+='<div class="wf-meta"><span><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><line x1="3" y1="9" x2="21" y2="9"/></svg> '+(w.nodes?w.nodes.length:0)+' nodes</span>';
782
+ h+='<span><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg> '+esc(w.createdAt?new Date(w.createdAt).toLocaleDateString():'')+'</span></div></div>';
783
+ h+='<span class="wf-badge '+(w.active?'active':'draft')+'">'+(w.active?'Active':'Draft')+'</span>';
784
+ h+='<div class="wf-acts">';
785
+ h+='<button class="btn btn-g btn-sm" onclick="event.stopPropagation();execWf(\''+w.id+'\')">Run</button>';
786
+ h+='<button class="btn btn-g btn-sm" onclick="event.stopPropagation();dupeWf(\''+w.id+'\')">Duplicate</button>';
787
+ h+='<button class="btn btn-d btn-sm" onclick="event.stopPropagation();delWf(\''+w.id+'\',\''+esc(w.name)+'\')">Delete</button>';
788
+ h+='</div></div>';
789
+ }
790
+ document.getElementById('wfList').innerHTML=h;
791
+ }
792
+ function filterWorkflows(){var q=document.getElementById('wfSearch').value.toLowerCase();
793
+ var f=_wfData.filter(function(w){return w.name.toLowerCase().includes(q)||(w.description||'').toLowerCase().includes(q)});
794
+ renderWorkflows(f);
795
+ }
796
+ function openWorkflowEditor(id){window.open('/workflow-editor.html'+(id?'?id='+id:''),'_blank')}
797
+ function execWf(id){toast('Executing workflow...','info');
798
+ fetch('/api/workflows/'+id+'/execute',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({})})
799
+ .then(function(r){return r.json()}).then(function(d){
800
+ if(d.error)toast('Error: '+d.error,'err');
801
+ else toast('Workflow '+(d.status==='success'?'completed':'failed')+' in '+(d.duration/1000).toFixed(1)+'s',d.status==='success'?'ok':'err');
802
+ }).catch(function(e){toast(e.message,'err')})}
803
+ function dupeWf(id){fetch('/api/workflows/'+id+'/duplicate',{method:'POST'}).then(function(r){return r.json()}).then(function(d){
804
+ if(d.id){toast('Duplicated','ok');loadWorkflows()}else toast('Error','err')}).catch(function(e){toast(e.message,'err')})}
805
+ function delWf(id,name){if(!confirm('Delete workflow "'+name+'"?'))return;
806
+ fetch('/api/workflows/'+id,{method:'DELETE'}).then(function(){toast('Deleted','ok');loadWorkflows()}).catch(function(e){toast(e.message,'err')})}
807
+ // WEBSOCKET
808
+ try{var ws=new WebSocket('ws://'+location.host);
809
+ ws.onopen=function(){document.getElementById('wsDot').className='dot on';document.getElementById('wsLabel').textContent='Connected'};
810
+ ws.onclose=function(){document.getElementById('wsDot').className='dot off';document.getElementById('wsLabel').textContent='Disconnected'};
811
+ ws.onerror=function(){document.getElementById('wsDot').className='dot off';document.getElementById('wsLabel').textContent='Error'};
812
+ ws.onmessage=function(ev){var m=JSON.parse(ev.data),o=document.getElementById('taskOut');
813
+ if(m.type==='state_update')o.innerHTML+='<span style="color:var(--yellow)">'+esc(m.payload.task)+'</span>\n';
814
+ else if(m.type==='log')o.innerHTML+='<span style="color:var(--dim)">'+esc(m.payload)+'</span>\n';
815
+ else if(m.type==='complete'){o.innerHTML+='\n<span style="color:var(--green)">Complete</span>\n'+esc(m.payload.result||'')+'\n';tBusy=0;document.getElementById('taskBtn').disabled=false}
816
+ else if(m.type==='error'){o.innerHTML+='<span style="color:var(--red)">Error: '+esc(m.payload.error)+'</span>\n';tBusy=0;document.getElementById('taskBtn').disabled=false}
817
+ o.scrollTop=o.scrollHeight};
818
+ setInterval(function(){if(ws&&ws.readyState===1)ws.send('{"type":"ping"}')},30000)}catch(e){}
819
+ // STATUS
820
+ function loadHome(){
821
+ fetch('/api/system/status').then(function(r){return r.json()}).then(function(s){
822
+ document.getElementById('hUptime').textContent=document.getElementById('siUp').textContent;
823
+ document.getElementById('hMem').textContent=s.memory.entries;
824
+ document.getElementById('hJobs').textContent=s.scheduler.jobs;
825
+ document.getElementById('hTasks').textContent=s.scheduler.jobs;
826
+ document.getElementById('hSess').textContent=s.chat.messages||'0';
827
+ document.getElementById('hAlerts').textContent='0';
828
+ document.getElementById('cntMem').textContent=s.memory.entries;
829
+ document.getElementById('cntJob').textContent=s.scheduler.jobs;
830
+ document.getElementById('siProv').textContent=s.provider||'-';
831
+ document.getElementById('siConn').textContent=s.connections;
832
+ var u=Math.floor(s.uptime),h=Math.floor(u/3600),m=Math.floor((u%3600)/60);
833
+ document.getElementById('siUp').textContent=h>0?h+'h '+m+'m':m+'m '+(u%60)+'s';
834
+ }).catch(function(){});
835
+ fetch('/api/settings').then(function(r){return r.json()}).then(function(s){
836
+ document.getElementById('hModel').textContent=s.llm.model?s.llm.model.split('/').pop():'-';
837
+ document.getElementById('modelBadge').textContent=s.llm.model||'?';
838
+ }).catch(function(){});
839
+ fetch('/api/skills').then(function(r){return r.json()}).then(function(sk){
840
+ var en=sk.filter(function(s){return s.enabled!==false});
841
+ var cfg=sk.filter(function(s){return s.configFields&&s.configFields.length>0});
842
+ document.getElementById('hSkillsStatus').textContent=en.length+'/'+sk.length;
843
+ var h='',svI='<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">';
844
+ var icons={email:svI+'<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>',telegram:svI+'<line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>',discord:svI+'<path d="M18 8a3 3 0 0 0-3-3H9a3 3 0 0 0-3 3v8a3 3 0 0 0 3 3h6a3 3 0 0 0 3-3z"/><circle cx="10" cy="12" r="1"/><circle cx="14" cy="12" r="1"/></svg>',slack:svI+'<rect x="13" y="2" width="3" height="8" rx="1.5"/><rect x="8" y="14" width="3" height="8" rx="1.5"/><rect x="2" y="8" width="8" height="3" rx="1.5"/><rect x="14" y="13" width="8" height="3" rx="1.5"/></svg>',whatsapp:svI+'<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/></svg>'};
845
+ for(var i=0;i<cfg.length;i++){var skl=cfg[i];if(skl.enabled!==false)h+='<div style="display:flex;align-items:center;gap:6px;padding:4px 10px;background:var(--bg3);border-radius:6px;font-size:12px">'+(icons[skl.name]||'')+'<span>'+skl.name+'</span></div>';}
846
+ if(h)document.getElementById('hSkillsList').innerHTML=h;
847
+ }).catch(function(){});
848
+ }
849
+ function poll(){loadHome();}
850
+ poll();setInterval(poll,15000);loadSett();
851
+ </script>
852
+ </body>
853
+ </html>