@aliwey/bmo 2.0.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 (100) hide show
  1. package/README.md +90 -0
  2. package/bin/bmo.js +188 -0
  3. package/cli.py +1129 -0
  4. package/config/__init__.py +0 -0
  5. package/config/__pycache__/__init__.cpython-313.pyc +0 -0
  6. package/config/__pycache__/settings.cpython-313.pyc +0 -0
  7. package/config/__pycache__/system-prompt.cpython-313.pyc +0 -0
  8. package/config/settings.py +104 -0
  9. package/config/system-prompt.json +18 -0
  10. package/core/__init__.py +0 -0
  11. package/core/__pycache__/__init__.cpython-313.pyc +0 -0
  12. package/core/__pycache__/bfp_a2a_bridge.cpython-313.pyc +0 -0
  13. package/core/__pycache__/bfp_agent.cpython-313.pyc +0 -0
  14. package/core/__pycache__/bfp_agent_card.cpython-313.pyc +0 -0
  15. package/core/__pycache__/bfp_connector.cpython-313.pyc +0 -0
  16. package/core/__pycache__/bfp_discovery.cpython-313.pyc +0 -0
  17. package/core/__pycache__/bfp_identity.cpython-313.pyc +0 -0
  18. package/core/__pycache__/bfp_tasks.cpython-313.pyc +0 -0
  19. package/core/__pycache__/bfp_transport.cpython-313.pyc +0 -0
  20. package/core/__pycache__/bmo_engine.cpython-313.pyc +0 -0
  21. package/core/__pycache__/bot_client.cpython-313.pyc +0 -0
  22. package/core/__pycache__/budget_tracker.cpython-313.pyc +0 -0
  23. package/core/__pycache__/cli_renderer.cpython-313.pyc +0 -0
  24. package/core/__pycache__/goal_runner.cpython-313.pyc +0 -0
  25. package/core/__pycache__/request_worker.cpython-313.pyc +0 -0
  26. package/core/__pycache__/security.cpython-313.pyc +0 -0
  27. package/core/__pycache__/shared_state.cpython-313.pyc +0 -0
  28. package/core/__pycache__/worker_manager.cpython-313.pyc +0 -0
  29. package/core/__pycache__/worker_multiproc.cpython-313.pyc +0 -0
  30. package/core/__pycache__/worker_protocol.cpython-313.pyc +0 -0
  31. package/core/__pycache__/worker_subprocess.cpython-313.pyc +0 -0
  32. package/core/bfp_a2a_bridge.py +399 -0
  33. package/core/bfp_agent.py +98 -0
  34. package/core/bfp_agent_card.py +161 -0
  35. package/core/bfp_connector.py +177 -0
  36. package/core/bfp_discovery.py +105 -0
  37. package/core/bfp_identity.py +83 -0
  38. package/core/bfp_tasks.py +70 -0
  39. package/core/bfp_transport.py +368 -0
  40. package/core/bmo_engine.py +405 -0
  41. package/core/bot_client.py +838 -0
  42. package/core/budget_tracker.py +62 -0
  43. package/core/cli_renderer.py +177 -0
  44. package/core/goal_runner.py +129 -0
  45. package/core/request_worker.py +242 -0
  46. package/core/security.py +42 -0
  47. package/core/shared_state.py +4 -0
  48. package/core/worker_manager.py +71 -0
  49. package/core/worker_multiproc.py +155 -0
  50. package/core/worker_protocol.py +30 -0
  51. package/core/worker_subprocess.py +222 -0
  52. package/handlers/__init__.py +0 -0
  53. package/handlers/__pycache__/__init__.cpython-313.pyc +0 -0
  54. package/handlers/__pycache__/messages.cpython-313.pyc +0 -0
  55. package/handlers/messages.py +2761 -0
  56. package/main.py +125 -0
  57. package/memory.md +43 -0
  58. package/models/__init__.py +0 -0
  59. package/models/__pycache__/__init__.cpython-313.pyc +0 -0
  60. package/models/__pycache__/chat_models.cpython-313.pyc +0 -0
  61. package/models/chat_models.py +143 -0
  62. package/package.json +50 -0
  63. package/registry/worker.js +108 -0
  64. package/registry/wrangler.toml +11 -0
  65. package/requirements.txt +13 -0
  66. package/scripts/bmo_init.js +115 -0
  67. package/scripts/postinstall.js +265 -0
  68. package/scripts/relay_cmd.js +276 -0
  69. package/scripts/web_cmd.js +136 -0
  70. package/setup.py +26 -0
  71. package/storage/__init__.py +0 -0
  72. package/storage/__pycache__/__init__.cpython-313.pyc +0 -0
  73. package/storage/__pycache__/sqlite_storage.cpython-313.pyc +0 -0
  74. package/storage/__pycache__/storage.cpython-313.pyc +0 -0
  75. package/storage/sqlite_storage.py +658 -0
  76. package/storage/storage.py +265 -0
  77. package/tools/__pycache__/bfp_relay.cpython-313.pyc +0 -0
  78. package/tools/__pycache__/get_session_summaries.cpython-313.pyc +0 -0
  79. package/tools/__pycache__/mcp_bridge.cpython-313.pyc +0 -0
  80. package/tools/__pycache__/mcp_server.cpython-313.pyc +0 -0
  81. package/tools/__pycache__/run_mcp_standalone.cpython-313.pyc +0 -0
  82. package/tools/__pycache__/task_registry.cpython-313.pyc +0 -0
  83. package/tools/__pycache__/test_mcp_connection.cpython-313.pyc +0 -0
  84. package/tools/bfp_relay.py +359 -0
  85. package/tools/bot.db +0 -0
  86. package/tools/get_session_summaries.py +45 -0
  87. package/tools/mcp_bridge.py +109 -0
  88. package/tools/mcp_server.py +531 -0
  89. package/tools/register_mcp_task.py +20 -0
  90. package/tools/run_detached.bat +32 -0
  91. package/tools/run_mcp_standalone.py +16 -0
  92. package/tools/task_registry.py +184 -0
  93. package/tools/test_mcp_connection.py +80 -0
  94. package/webchat/package-lock.json +1528 -0
  95. package/webchat/package.json +12 -0
  96. package/webchat/public/app.js +1293 -0
  97. package/webchat/public/index.html +226 -0
  98. package/webchat/public/index.html.bak +416 -0
  99. package/webchat/public/styles.css +2435 -0
  100. package/webchat/server.js +645 -0
@@ -0,0 +1,226 @@
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>BMO — WebChat</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Cairo:wght@400;500;600;700&family=Outfit:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&family=DM+Serif+Display:ital@0;1&display=swap" rel="stylesheet">
10
+ <script src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"></script>
11
+ <link rel="stylesheet" href="styles.css">
12
+ </head>
13
+ <body>
14
+
15
+ <div id="connBanner">
16
+ <i data-lucide="wifi-off"></i>
17
+ Connection lost — reconnecting...
18
+ </div>
19
+
20
+ <div class="app-container">
21
+ <aside class="sidebar" id="sidebar" aria-label="Sessions and profile navigation">
22
+ <div class="sidebar-header">
23
+ <h2>
24
+ <i data-lucide="message-square"></i>
25
+ Sessions
26
+ </h2>
27
+ <button id="sidebarClose">
28
+ <i data-lucide="panel-left-close"></i>
29
+ </button>
30
+ </div>
31
+ <div class="session-list" id="sessionList"></div>
32
+ <div class="sidebar-footer">
33
+ <button id="btnProfile" class="profile-btn">
34
+ <i data-lucide="user-circle"></i>
35
+ <span>Profile</span>
36
+ </button>
37
+ </div>
38
+ </aside>
39
+
40
+ <div class="sidebar-overlay" id="sidebarOverlay"></div>
41
+
42
+ <main class="main-panel">
43
+ <header class="header-row" aria-label="Chat session header">
44
+ <button id="sidebarToggle" class="icon-btn">
45
+ <i data-lucide="panel-left-open"></i>
46
+ </button>
47
+ <div class="status-indicator">
48
+ <span class="status-dot" id="statusDot"></span>
49
+ <span class="status-text" id="statusText">Online</span>
50
+ </div>
51
+ <div class="header-right">
52
+ <span class="model-badge" id="modelBadge">
53
+ <i data-lucide="cpu"></i>
54
+ <span id="modelBadgeText">big-pickle</span>
55
+ </span>
56
+ <span class="mode-badge">
57
+ <i data-lucide="zap"></i>
58
+ Execute
59
+ </span>
60
+ <button id="reloadBtn" class="icon-btn" title="Reload">
61
+ <i data-lucide="rotate-ccw"></i>
62
+ </button>
63
+ <button id="themeToggle" class="icon-btn" title="Toggle theme">
64
+ <i data-lucide="sun"></i>
65
+ </button>
66
+ </div>
67
+ </header>
68
+
69
+ <div class="chat-area" id="chatArea">
70
+ <div class="empty" id="emptyState">
71
+ <h1 class="empty-wordmark" lang="en">Hey, I'm <span class="empty-wordmark-accent">BMO</span>.</h1>
72
+ <div class="empty-divider" aria-hidden="true"></div>
73
+ <p class="empty-sub">What can I help you with today?</p>
74
+ <div class="empty-suggestions">
75
+ <button class="suggestion-card" type="button" data-suggestion="Plan my day with focused blocks, breaks, and the top 3 priorities.">
76
+ <span class="suggestion-icon" aria-hidden="true"><i data-lucide="calendar-days"></i></span>
77
+ <span class="suggestion-body">
78
+ <span class="suggestion-title">Plan my day</span>
79
+ <span class="suggestion-desc">Build a focused schedule with priorities</span>
80
+ </span>
81
+ </button>
82
+ <button class="suggestion-card" type="button" data-suggestion="Write a clean, well-commented Python script for the following task: ">
83
+ <span class="suggestion-icon" aria-hidden="true"><i data-lucide="code-2"></i></span>
84
+ <span class="suggestion-body">
85
+ <span class="suggestion-title">Write a Python script</span>
86
+ <span class="suggestion-desc">Get a starting point you can edit</span>
87
+ </span>
88
+ </button>
89
+ <button class="suggestion-card" type="button" data-suggestion="Explain this concept in plain language with a short example: ">
90
+ <span class="suggestion-icon" aria-hidden="true"><i data-lucide="lightbulb"></i></span>
91
+ <span class="suggestion-body">
92
+ <span class="suggestion-title">Explain a concept</span>
93
+ <span class="suggestion-desc">Plain language with an example</span>
94
+ </span>
95
+ </button>
96
+ </div>
97
+ </div>
98
+ </div>
99
+
100
+ <div class="typing-indicator" id="typingIndicator">
101
+ <span></span><span></span><span></span>
102
+ </div>
103
+
104
+ <nav class="toolbar" id="toolbar" aria-label="Chat actions">
105
+ <button id="btnNew">
106
+ <i data-lucide="plus"></i>
107
+ New
108
+ </button>
109
+ <button id="btnSummarize">
110
+ <i data-lucide="save"></i>
111
+ Save
112
+ </button>
113
+ <button id="btnReset">
114
+ <i data-lucide="trash-2"></i>
115
+ Clear
116
+ </button>
117
+ <button id="btnModels">
118
+ <i data-lucide="cpu"></i>
119
+ Models
120
+ </button>
121
+ <button id="btnAgents">
122
+ <i data-lucide="users"></i>
123
+ Agents
124
+ </button>
125
+ </nav>
126
+
127
+ <div class="input-area">
128
+ <input type="text" id="msgInput" placeholder="Type your message..." autofocus inputmode="text" autocapitalize="sentences" autocomplete="off" spellcheck="true" enterkeyhint="send" aria-label="Message BMO">
129
+ <button id="sendBtn" disabled aria-label="Send message">
130
+ <i data-lucide="send"></i>
131
+ </button>
132
+ </div>
133
+ </main>
134
+ </div>
135
+
136
+ <!-- Models Modal -->
137
+ <div class="modal-overlay" id="modelsModal" role="dialog" aria-modal="true" aria-label="Select model">
138
+ <div class="modal-content">
139
+ <div class="modal-header">
140
+ <h3><i data-lucide="cpu"></i> Select Model</h3>
141
+ <button class="modal-close" data-close="modelsModal"><i data-lucide="x"></i></button>
142
+ </div>
143
+ <div class="modal-body" id="modelsBody">
144
+ <div class="modal-loading"><i data-lucide="loader-2" class="spin"></i> Loading models...</div>
145
+ </div>
146
+ </div>
147
+ </div>
148
+
149
+ <!-- Agents Modal -->
150
+ <div class="modal-overlay" id="agentsModal" role="dialog" aria-modal="true" aria-label="Select agent">
151
+ <div class="modal-content">
152
+ <div class="modal-header">
153
+ <h3><i data-lucide="users"></i> Select Agent</h3>
154
+ <button class="modal-close" data-close="agentsModal"><i data-lucide="x"></i></button>
155
+ </div>
156
+ <div class="modal-body" id="agentsBody">
157
+ <div class="modal-loading"><i data-lucide="loader-2" class="spin"></i> Loading agents...</div>
158
+ </div>
159
+ </div>
160
+ </div>
161
+
162
+ <!-- Profile Modal -->
163
+ <div class="modal-overlay" id="profileModal" role="dialog" aria-modal="true" aria-label="Profile">
164
+ <div class="modal-content">
165
+ <div class="modal-header">
166
+ <h3><i data-lucide="user-circle"></i> Profile</h3>
167
+ <button class="modal-close" data-close="profileModal"><i data-lucide="x"></i></button>
168
+ </div>
169
+ <div class="modal-body" id="profileBody">
170
+ <div class="profile-info">
171
+ <div class="profile-avatar">
172
+ <i data-lucide="user"></i>
173
+ </div>
174
+ <div class="profile-details">
175
+ <h4>BMO User</h4>
176
+ <p class="profile-id" id="profileId">ID: —</p>
177
+ </div>
178
+ </div>
179
+ <div class="profile-stats">
180
+ <div class="stat-card">
181
+ <i data-lucide="message-square"></i>
182
+ <span id="profileSessions">0</span>
183
+ <small>Sessions</small>
184
+ </div>
185
+ <div class="stat-card">
186
+ <i data-lucide="cpu"></i>
187
+ <span id="profileModel">Default</span>
188
+ <small>Model</small>
189
+ </div>
190
+ <div class="stat-card">
191
+ <i data-lucide="users"></i>
192
+ <span id="profileAgent">None</span>
193
+ <small>Agent</small>
194
+ </div>
195
+ </div>
196
+ <div class="profile-section">
197
+ <h4>Current Settings</h4>
198
+ <div class="setting-row">
199
+ <span>Theme</span>
200
+ <span class="setting-value" id="profileTheme">Dark</span>
201
+ </div>
202
+ <div class="setting-row">
203
+ <span>Mode</span>
204
+ <span class="setting-value">Execute</span>
205
+ </div>
206
+ </div>
207
+ </div>
208
+ </div>
209
+ </div>
210
+
211
+ <script src="/socket.io/socket.io.js"></script>
212
+ <script src="app.js"></script>
213
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
214
+ <script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/core.min.js"></script>
215
+ <script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/python.min.js"></script>
216
+ <script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/javascript.min.js"></script>
217
+ <script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/typescript.min.js"></script>
218
+ <script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/bash.min.js"></script>
219
+ <script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/json.min.js"></script>
220
+ <script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/css.min.js"></script>
221
+ <script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/xml.min.js"></script>
222
+ <script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/sql.min.js"></script>
223
+ <script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/diff.min.js"></script>
224
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/styles/atom-one-dark.min.css">
225
+ </body>
226
+ </html>
@@ -0,0 +1,416 @@
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>BMO — WebChat</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Cairo:wght@400;600;700;900&display=swap" rel="stylesheet">
10
+ <style>
11
+ :root {
12
+ --bg-primary: #12151c;
13
+ --bg-secondary: #1a1e28;
14
+ --bg-card: #1e2330;
15
+ --gold: #d4a843;
16
+ --gold-light: #f0c27a;
17
+ --gold-dark: #a87d2e;
18
+ --text-primary: #e8e6e0;
19
+ --text-secondary: #9a9588;
20
+ --text-muted: #5c574e;
21
+ --border: #2a2e3a;
22
+ --success: #27ae60;
23
+ --danger: #e74c3c;
24
+ }
25
+ * { margin: 0; padding: 0; box-sizing: border-box; }
26
+ body {
27
+ font-family: 'Cairo', sans-serif;
28
+ background: var(--bg-primary);
29
+ color: var(--text-primary);
30
+ height: 100vh; overflow: hidden;
31
+ }
32
+ .app-layout { display: flex; height: 100vh; }
33
+
34
+ .sidebar {
35
+ width: 280px; background: var(--bg-secondary);
36
+ border-right: 1px solid var(--border);
37
+ display: flex; flex-direction: column; flex-shrink: 0;
38
+ transition: margin-left 0.3s ease;
39
+ overflow-y: auto;
40
+ }
41
+ .sidebar.collapsed { margin-left: -280px; }
42
+ .sidebar-header {
43
+ padding: 14px 16px; display: flex; justify-content: space-between;
44
+ align-items: center; border-bottom: 1px solid var(--border);
45
+ }
46
+ .sidebar-header h2 { font-size: 15px; color: var(--text-primary); }
47
+ .sidebar-header button {
48
+ background: none; border: none; color: var(--text-muted);
49
+ font-size: 22px; cursor: pointer;
50
+ }
51
+ .session-list { flex: 1; overflow-y: auto; padding: 8px; }
52
+ .session-item {
53
+ padding: 10px 12px; border-radius: 10px; cursor: pointer;
54
+ margin-bottom: 4px; transition: background 0.15s;
55
+ }
56
+ .session-item:hover { background: var(--bg-card); }
57
+ .session-item.active { background: var(--bg-card); border-left: 3px solid var(--gold); }
58
+ .session-title { font-size: 14px; color: var(--text-primary); font-weight: 600; }
59
+ .session-meta { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
60
+
61
+ .main-panel { flex: 1; display: flex; flex-direction: column; min-width: 0; }
62
+
63
+ .header-row {
64
+ display: flex; align-items: center; gap: 10px;
65
+ padding: 8px 16px; background: var(--bg-secondary);
66
+ border-bottom: 1px solid var(--border); flex-shrink: 0;
67
+ }
68
+ .icon-btn {
69
+ background: none; border: none; color: var(--text-secondary);
70
+ font-size: 18px; cursor: pointer; padding: 4px 8px; border-radius: 6px;
71
+ }
72
+ .icon-btn:hover { color: var(--text-primary); background: var(--bg-card); }
73
+ .mode-badge {
74
+ font-size: 12px; padding: 2px 10px; border-radius: 10px;
75
+ background: var(--gold-dark); color: #fff; margin-left: auto;
76
+ }
77
+
78
+ .status-dot {
79
+ display: inline-block; width: 8px; height: 8px;
80
+ border-radius: 50%; background: var(--gold);
81
+ box-shadow: 0 0 8px rgba(212, 168, 67, 0.4);
82
+ transition: background 0.3s, box-shadow 0.3s;
83
+ vertical-align: middle;
84
+ }
85
+ .status-dot.typing {
86
+ background: var(--gold-light);
87
+ box-shadow: 0 0 12px rgba(240, 194, 122, 0.6);
88
+ animation: pulse 0.8s infinite;
89
+ }
90
+ .status-dot.offline { background: #5c574e; box-shadow: none; }
91
+ @keyframes pulse {
92
+ 0%, 100% { opacity: 1; transform: scale(1); }
93
+ 50% { opacity: 0.5; transform: scale(0.8); }
94
+ }
95
+
96
+ .chat-area {
97
+ flex: 1; overflow-y: auto; padding: 20px 16px;
98
+ display: flex; flex-direction: column; gap: 12px;
99
+ scrollbar-width: thin; scrollbar-color: var(--border) transparent;
100
+ }
101
+ .chat-area::-webkit-scrollbar { width: 4px; }
102
+ .chat-area::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
103
+ .chat-area .empty { margin: auto; text-align: center; color: var(--text-muted); font-size: 14px; }
104
+
105
+ .msg { max-width: 82%; padding: 12px 18px; border-radius: 16px; line-height: 1.7; font-size: 15px; font-weight: 500; word-wrap: break-word; animation: fadeIn 0.3s ease; }
106
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
107
+ .msg.user { align-self: flex-end; background: linear-gradient(135deg, var(--gold), var(--gold-dark)); color: #12151c; border-bottom-left-radius: 4px; box-shadow: 0 2px 12px rgba(212, 168, 67, 0.15); }
108
+ .msg.bmo { align-self: flex-start; background: var(--bg-card); border: 1px solid var(--border); border-bottom-right-radius: 4px; color: var(--text-primary); }
109
+ .msg .time { font-size: 11px; font-weight: 400; color: inherit; opacity: 0.5; margin-top: 6px; text-align: right; direction: ltr; }
110
+ .msg.bmo .time { text-align: left; }
111
+
112
+ .code-block { background: var(--bg-primary); border: 1px solid var(--border); border-radius: 8px; padding: 12px; margin: 8px 0; font-family: 'Consolas', 'Monaco', monospace; font-size: 13px; overflow-x: auto; direction: ltr; text-align: left; }
113
+
114
+ .toolbar {
115
+ display: flex; gap: 6px; padding: 8px 16px;
116
+ background: var(--bg-secondary); border-top: 1px solid var(--border);
117
+ flex-shrink: 0; overflow-x: auto;
118
+ }
119
+ .toolbar button {
120
+ padding: 6px 14px; border-radius: 20px; border: 1px solid var(--border);
121
+ background: var(--bg-card); color: var(--text-primary); font-size: 13px;
122
+ cursor: pointer; white-space: nowrap; transition: all 0.15s;
123
+ font-family: inherit;
124
+ }
125
+ .toolbar button:hover { border-color: var(--gold); color: var(--gold-light); }
126
+
127
+ .input-area {
128
+ padding: 14px 16px 18px;
129
+ background: var(--bg-secondary);
130
+ border-top: 1px solid var(--border);
131
+ display: flex; gap: 10px; flex-shrink: 0;
132
+ }
133
+ .input-area input {
134
+ flex: 1; padding: 12px 18px; border-radius: 12px;
135
+ border: 1px solid var(--border); background: var(--bg-primary);
136
+ color: var(--text-primary); font-family: 'Cairo', sans-serif;
137
+ font-size: 15px; font-weight: 500; outline: none;
138
+ transition: border-color 0.2s, box-shadow 0.2s;
139
+ }
140
+ .input-area input:focus { border-color: var(--gold); box-shadow: 0 0 0 3px rgba(212, 168, 67, 0.1); }
141
+ .input-area input::placeholder { color: var(--text-muted); font-weight: 400; }
142
+ .input-area button {
143
+ width: 48px; height: 48px; border-radius: 12px; border: none;
144
+ background: linear-gradient(135deg, var(--gold), var(--gold-dark));
145
+ color: #12151c; font-size: 20px; font-weight: 700; cursor: pointer;
146
+ transition: transform 0.15s, opacity 0.2s, box-shadow 0.2s;
147
+ display: flex; align-items: center; justify-content: center;
148
+ }
149
+ .input-area button:hover { opacity: 0.9; transform: scale(1.05); box-shadow: 0 0 20px rgba(212, 168, 67, 0.2); }
150
+ .input-area button:active { transform: scale(0.95); }
151
+ .input-area button:disabled { opacity: 0.3; cursor: not-allowed; transform: none; box-shadow: none; }
152
+
153
+ .typing-indicator { align-self: flex-start; display: none; background: var(--bg-card); border: 1px solid var(--border); padding: 12px 20px; border-radius: 16px; border-bottom-right-radius: 4px; margin-top: -4px; }
154
+ .typing-indicator.show { display: flex; gap: 5px; align-items: center; }
155
+ .typing-indicator span { width: 8px; height: 8px; background: var(--gold); border-radius: 50%; animation: bounce 1.2s infinite; }
156
+ .typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
157
+ .typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
158
+ @keyframes bounce { 0%, 60%, 100% { transform: translateY(0); } 30% { transform: translateY(-8px); } }
159
+
160
+ #connBanner { display: none; position: fixed; top: 0; left: 0; right: 0; background: var(--danger); color: #fff; text-align: center; padding: 8px; font-size: 13px; font-weight: 600; z-index: 100; }
161
+ #connBanner.show { display: block; }
162
+ </style>
163
+ </head>
164
+ <body>
165
+
166
+ <div id="connBanner">⚠️ Connection lost — reconnecting...</div>
167
+ <div class="app-layout">
168
+ <aside class="sidebar" id="sidebar">
169
+ <div class="sidebar-header">
170
+ <h2>💬 Sessions</h2>
171
+ <button id="sidebarClose">&times;</button>
172
+ </div>
173
+ <div class="session-list" id="sessionList"></div>
174
+ </aside>
175
+
176
+ <main class="main-panel">
177
+ <div class="header-row">
178
+ <button id="sidebarToggle" class="icon-btn">☰</button>
179
+ <span class="status-dot" id="statusDot"></span>
180
+ <span id="statusText">Online</span>
181
+ <span class="mode-badge" id="modeBadge">⚡ Execute</span>
182
+ <button id="reloadBtn" class="icon-btn">🔄</button>
183
+ </div>
184
+
185
+ <div class="chat-area" id="chatArea">
186
+ <div class="empty" id="emptyState">Send a message to start ✨</div>
187
+ </div>
188
+
189
+ <div class="typing-indicator" id="typingIndicator">
190
+ <span></span><span></span><span></span>
191
+ </div>
192
+
193
+ <div class="toolbar" id="toolbar">
194
+ <button id="btnNew">🆕 New</button>
195
+ <button id="btnSummarize">💾 Save</button>
196
+ <button id="btnReset">🗑️ Clear</button>
197
+ <button id="btnModels">🤖 Models</button>
198
+ <button id="btnAgents">🎭 Agents</button>
199
+ </div>
200
+
201
+ <div class="input-area">
202
+ <input type="text" id="msgInput" placeholder="Type your message..." autofocus>
203
+ <button id="sendBtn" disabled>➤</button>
204
+ </div>
205
+ </main>
206
+ </div>
207
+
208
+ <script src="/socket.io/socket.io.js"></script>
209
+ <script>
210
+ const socket = io();
211
+ const chatArea = document.getElementById('chatArea');
212
+ const emptyState = document.getElementById('emptyState');
213
+ const input = document.getElementById('msgInput');
214
+ const sendBtn = document.getElementById('sendBtn');
215
+ const typingEl = document.getElementById('typingIndicator');
216
+ const statusDot = document.getElementById('statusDot');
217
+ const statusText = document.getElementById('statusText');
218
+ const connBanner = document.getElementById('connBanner');
219
+ const sessionList = document.getElementById('sessionList');
220
+
221
+ let sessions = [];
222
+ let activeSessionId = null;
223
+
224
+ // ── Socket.IO ──
225
+
226
+ socket.on('connect', () => {
227
+ connBanner.classList.remove('show');
228
+ if (activeSessionId) socket.emit('set_session', activeSessionId);
229
+ setStatus('online');
230
+ });
231
+ socket.on('disconnect', () => { connBanner.classList.add('show'); setStatus('offline'); });
232
+
233
+ socket.on('message', (msg) => {
234
+ if (msg.session_id && msg.session_id !== activeSessionId) return;
235
+ emptyState.style.display = 'none';
236
+ const div = document.createElement('div');
237
+ div.className = 'msg ' + msg.role;
238
+ div.innerHTML = renderMessage(msg.text) + `<div class="time">${new Date(msg.time).toLocaleTimeString()}</div>`;
239
+ chatArea.appendChild(div);
240
+ chatArea.scrollTop = chatArea.scrollHeight;
241
+ });
242
+
243
+ socket.on('bmo_status', (data) => {
244
+ if (data.status === 'typing') {
245
+ setStatus('typing');
246
+ } else {
247
+ setStatus('online');
248
+ }
249
+ });
250
+
251
+ // ── Session Sidebar ──
252
+
253
+ async function loadSessions() {
254
+ try {
255
+ const r = await fetch('/api/sessions?chat_id=732356803');
256
+ sessions = await r.json();
257
+ renderSidebar();
258
+ const active = sessions.find(s => s.is_active);
259
+ if (active) activeSessionId = active.session_id;
260
+ else if (sessions.length) activeSessionId = sessions[0].session_id;
261
+ } catch (e) { /* ignore polling errors */ }
262
+ }
263
+
264
+ function renderSidebar() {
265
+ sessionList.innerHTML = sessions.map(s => `
266
+ <div class="session-item ${s.session_id === activeSessionId ? 'active' : ''}"
267
+ data-sid="${s.session_id}">
268
+ <div class="session-title">${escapeHtml(s.title)}</div>
269
+ <div class="session-meta">${s.msg_count} msgs</div>
270
+ </div>
271
+ `).join('');
272
+ sessionList.querySelectorAll('.session-item').forEach(el => {
273
+ el.addEventListener('click', () => switchSession(el.dataset.sid));
274
+ });
275
+ }
276
+
277
+ async function switchSession(sid) {
278
+ await fetch('/api/switch-session', {
279
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
280
+ body: JSON.stringify({ chat_id: 732356803, session_id: sid })
281
+ });
282
+ activeSessionId = sid;
283
+ await loadSessions();
284
+ await loadMessages(sid);
285
+ socket.emit('set_session', sid);
286
+ }
287
+
288
+ // ── Messages ──
289
+
290
+ async function loadMessages(sid) {
291
+ if (!sid) { chatArea.innerHTML = ''; emptyState.style.display = 'block'; return; }
292
+ try {
293
+ const r = await fetch(`/api/messages?session_id=${sid}&limit=100`);
294
+ const msgs = await r.json();
295
+ chatArea.innerHTML = '';
296
+ if (msgs.length === 0) { emptyState.style.display = 'block'; return; }
297
+ emptyState.style.display = 'none';
298
+ msgs.forEach(m => {
299
+ const div = document.createElement('div');
300
+ div.className = 'msg ' + (m.sender === 'user' ? 'user' : 'bmo');
301
+ div.innerHTML = renderMessage(m.content) + `<div class="time">${new Date(m.timestamp * 1000).toLocaleTimeString()}</div>`;
302
+ chatArea.appendChild(div);
303
+ });
304
+ chatArea.scrollTop = chatArea.scrollHeight;
305
+ } catch (e) { console.error('Failed to load messages:', e); }
306
+ }
307
+
308
+ function renderMessage(text) {
309
+ return text.replace(/<pre>([\s\S]*?)<\/pre>/g, '<div class="code-block"><code>$1</code></div>').replace(/\n/g, '<br>');
310
+ }
311
+
312
+ // ── Toolbar ──
313
+
314
+ document.getElementById('btnNew').addEventListener('click', async () => {
315
+ const r = await fetch('/api/new-session', {
316
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
317
+ body: JSON.stringify({ chat_id: 732356803 })
318
+ });
319
+ const data = await r.json();
320
+ await switchSession(data.session_id);
321
+ });
322
+
323
+ document.getElementById('btnReset').addEventListener('click', async () => {
324
+ if (!activeSessionId) return;
325
+ await fetch('/api/clear', {
326
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
327
+ body: JSON.stringify({ session_id: activeSessionId })
328
+ });
329
+ chatArea.innerHTML = '';
330
+ emptyState.style.display = 'block';
331
+ });
332
+
333
+ document.getElementById('btnSummarize').addEventListener('click', async () => {
334
+ if (!activeSessionId) return;
335
+ statusText.textContent = 'Summarizing...';
336
+ const r = await fetch('/api/summarize', {
337
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
338
+ body: JSON.stringify({ session_id: activeSessionId })
339
+ });
340
+ const data = await r.json();
341
+ statusText.textContent = 'Online';
342
+ if (data.summary) {
343
+ const div = document.createElement('div');
344
+ div.className = 'msg bmo';
345
+ div.innerHTML = '<b>📋 Summary</b><br>' + data.summary.slice(0, 500);
346
+ chatArea.appendChild(div);
347
+ }
348
+ });
349
+
350
+ document.getElementById('btnModels').addEventListener('click', async () => {
351
+ try {
352
+ const r = await fetch('/api/models');
353
+ const data = await r.json();
354
+ alert('Models: ' + JSON.stringify(data, null, 2).slice(0, 500));
355
+ } catch (e) {
356
+ alert('Failed to fetch models: ' + e.message);
357
+ }
358
+ });
359
+
360
+ document.getElementById('btnAgents').addEventListener('click', async () => {
361
+ try {
362
+ const r = await fetch('/api/agents?chat_id=732356803');
363
+ const agents = await r.json();
364
+ const names = agents.map(a => '\u2022 ' + (a.name || 'Unnamed') + ': ' + (a.description || 'No description')).join('\n');
365
+ alert('Custom Agents:\n' + (names || 'No custom agents'));
366
+ } catch (e) {
367
+ alert('Failed to fetch agents: ' + e.message);
368
+ }
369
+ });
370
+
371
+ // ── Input & Send ──
372
+
373
+ input.addEventListener('input', () => { sendBtn.disabled = !input.value.trim(); });
374
+ input.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !sendBtn.disabled) send(); });
375
+ sendBtn.addEventListener('click', send);
376
+
377
+ function send() {
378
+ const text = input.value.trim();
379
+ if (!text) return;
380
+ socket.emit('user_message', { text, session_id: activeSessionId });
381
+ input.value = '';
382
+ sendBtn.disabled = true;
383
+ setStatus('typing');
384
+ }
385
+
386
+ // ── Helpers ──
387
+
388
+ function escapeHtml(str) { const d = document.createElement('div'); d.textContent = str; return d.innerHTML; }
389
+
390
+ function setStatus(state) {
391
+ statusDot.className = 'status-dot';
392
+ if (state === 'typing') {
393
+ statusDot.classList.add('typing');
394
+ statusText.textContent = 'Typing...';
395
+ typingEl.classList.add('show');
396
+ } else if (state === 'online') {
397
+ statusText.textContent = 'Online';
398
+ typingEl.classList.remove('show');
399
+ } else {
400
+ statusDot.classList.add('offline');
401
+ statusText.textContent = 'Offline';
402
+ typingEl.classList.remove('show');
403
+ }
404
+ }
405
+
406
+ // ── Init ──
407
+
408
+ document.getElementById('sidebarToggle').onclick = () => document.getElementById('sidebar').classList.toggle('collapsed');
409
+ document.getElementById('sidebarClose').onclick = () => document.getElementById('sidebar').classList.add('collapsed');
410
+ document.getElementById('reloadBtn').onclick = () => location.reload();
411
+
412
+ setInterval(loadSessions, 30000);
413
+ loadSessions();
414
+ </script>
415
+ </body>
416
+ </html>