From 05c7abf496ba8a01cdafa0fb923bf59b9bb436ee Mon Sep 17 00:00:00 2001 From: Echo Date: Sat, 14 Feb 2026 08:26:57 +0000 Subject: [PATCH] Update ashboard, dashboard (~2) --- dashboard/api.py | 124 +++++++++++- dashboard/eco.html | 490 +++++++++++++++++++++++++++++++++------------ 2 files changed, 476 insertions(+), 138 deletions(-) diff --git a/dashboard/api.py b/dashboard/api.py index 1199b20..6f0241b 100644 --- a/dashboard/api.py +++ b/dashboard/api.py @@ -303,6 +303,10 @@ class TaskBoardHandler(SimpleHTTPRequestHandler): self.handle_workspace_logs() elif self.path == '/api/eco/status' or self.path.startswith('/api/eco/status?'): self.handle_eco_status() + elif self.path == '/api/eco/sessions' or self.path.startswith('/api/eco/sessions?'): + self.handle_eco_sessions() + elif self.path.startswith('/api/eco/sessions/content'): + self.handle_eco_session_content() elif self.path.startswith('/api/eco/logs'): self.handle_eco_logs() elif self.path == '/api/eco/doctor': @@ -2016,15 +2020,119 @@ class TaskBoardHandler(SimpleHTTPRequestHandler): services.append(info) - # Active sessions - sessions = [] - if ECHO_SESSIONS_FILE.exists(): - try: - sessions = json.loads(ECHO_SESSIONS_FILE.read_text()) - except Exception: - pass + self.send_json({'services': services}) + except Exception as e: + self.send_json({'error': str(e)}, 500) - self.send_json({'services': services, 'sessions': sessions}) + def _eco_channel_map(self): + """Build channel_id -> {name, platform, is_group} from config.json.""" + config_file = ECHO_CORE_DIR / 'config.json' + m = {} + try: + cfg = json.loads(config_file.read_text()) + for name, ch in cfg.get('channels', {}).items(): + m[str(ch['id'])] = {'name': name, 'platform': 'discord'} + for name, ch in cfg.get('telegram_channels', {}).items(): + m[str(ch['id'])] = {'name': name, 'platform': 'telegram'} + for name, ch in cfg.get('whatsapp_channels', {}).items(): + m[str(ch['id'])] = {'name': name, 'platform': 'whatsapp', 'is_group': True} + for admin_id in cfg.get('bot', {}).get('admins', []): + m.setdefault(str(admin_id), {'name': f'TG DM', 'platform': 'telegram'}) + wa_owner = cfg.get('whatsapp', {}).get('owner', '') + if wa_owner: + m.setdefault(f'wa-{wa_owner}', {'name': 'WA Owner', 'platform': 'whatsapp'}) + except Exception: + pass + return m + + def _eco_enrich_sessions(self): + """Return enriched sessions list sorted by last_message_at desc.""" + raw = {} + if ECHO_SESSIONS_FILE.exists(): + try: + raw = json.loads(ECHO_SESSIONS_FILE.read_text()) + except Exception: + pass + cmap = self._eco_channel_map() + sessions = [] + if isinstance(raw, dict): + for ch_id, sdata in raw.items(): + if 'MagicMock' in ch_id: + continue + entry = dict(sdata) if isinstance(sdata, dict) else {} + entry['channel_id'] = ch_id + if ch_id in cmap: + entry['platform'] = cmap[ch_id]['platform'] + entry['channel_name'] = cmap[ch_id]['name'] + entry['is_group'] = cmap[ch_id].get('is_group', False) + elif ch_id.startswith('wa-') or '@g.us' in ch_id or '@s.whatsapp.net' in ch_id: + entry['platform'] = 'whatsapp' + entry['is_group'] = '@g.us' in ch_id + entry['channel_name'] = ('WA Grup' if entry['is_group'] else 'WA DM') + elif ch_id.isdigit() and len(ch_id) >= 17: + entry['platform'] = 'discord' + entry['channel_name'] = 'Discord #' + ch_id[-6:] + elif ch_id.isdigit(): + entry['platform'] = 'telegram' + entry['channel_name'] = 'TG ' + ch_id + else: + entry['platform'] = 'unknown' + entry['channel_name'] = ch_id[:20] + sessions.append(entry) + sessions.sort(key=lambda s: s.get('last_message_at', ''), reverse=True) + return sessions + + def handle_eco_sessions(self): + """Return enriched sessions list.""" + try: + self.send_json({'sessions': self._eco_enrich_sessions()}) + except Exception as e: + self.send_json({'error': str(e)}, 500) + + def handle_eco_session_content(self): + """Return conversation messages from a session transcript.""" + try: + params = parse_qs(urlparse(self.path).query) + session_id = params.get('id', [''])[0] + if not session_id or '/' in session_id or '..' in session_id: + self.send_json({'error': 'Invalid session id'}, 400) + return + + transcript = Path.home() / '.claude' / 'projects' / '-home-moltbot-echo-core' / f'{session_id}.jsonl' + if not transcript.exists(): + self.send_json({'messages': [], 'error': 'Transcript not found'}) + return + + messages = [] + for line in transcript.read_text().splitlines(): + try: + d = json.loads(line) + except Exception: + continue + t = d.get('type', '') + if t == 'user': + msg = d.get('message', {}) + content = msg.get('content', '') + if isinstance(content, str): + # Strip [EXTERNAL CONTENT] wrappers + text = content.replace('[EXTERNAL CONTENT]\n', '').replace('\n[END EXTERNAL CONTENT]', '').strip() + if text: + messages.append({'role': 'user', 'text': text[:2000]}) + elif t == 'assistant': + msg = d.get('message', {}) + content = msg.get('content', '') + if isinstance(content, list): + parts = [] + for block in content: + if block.get('type') == 'text': + parts.append(block['text']) + text = '\n'.join(parts).strip() + if text: + messages.append({'role': 'assistant', 'text': text[:2000]}) + elif isinstance(content, str) and content.strip(): + messages.append({'role': 'assistant', 'text': content[:2000]}) + + self.send_json({'messages': messages}) except Exception as e: self.send_json({'error': str(e)}, 500) diff --git a/dashboard/eco.html b/dashboard/eco.html index 1061894..4762693 100644 --- a/dashboard/eco.html +++ b/dashboard/eco.html @@ -34,9 +34,9 @@ margin-top: var(--space-1); } - /* Section */ + /* Section (collapsible) */ .section { - margin-bottom: var(--space-5); + margin-bottom: var(--space-4); } .section-header { @@ -44,6 +44,20 @@ justify-content: space-between; align-items: center; margin-bottom: var(--space-3); + cursor: pointer; + user-select: none; + } + + .section.collapsed .section-body { display: none; } + .section.collapsed .section-header { margin-bottom: 0; } + .section.collapsed .sec-chev { transform: rotate(180deg); } + + .sec-chev { + width: 16px; + height: 16px; + color: var(--text-muted); + transition: transform var(--transition-fast); + flex-shrink: 0; } .section-title { @@ -132,37 +146,12 @@ padding: var(--space-1) var(--space-3); } - /* Sessions table */ - .sessions-table { - width: 100%; - border-collapse: collapse; - font-size: var(--text-sm); - } - - .sessions-table th, - .sessions-table td { - padding: var(--space-2) var(--space-3); - text-align: left; - border-bottom: 1px solid var(--border); - } - - .sessions-table th { - color: var(--text-muted); - font-weight: 500; - font-size: var(--text-xs); - text-transform: uppercase; - letter-spacing: 0.05em; - } - - .sessions-table td { - color: var(--text-secondary); - } - + /* Sessions */ .sessions-empty { text-align: center; - padding: var(--space-5); + padding: var(--space-4); color: var(--text-muted); - font-size: var(--text-sm); + font-size: var(--text-xs); } .sessions-card { @@ -172,6 +161,160 @@ overflow: hidden; } + .session-item { + border-bottom: 1px solid var(--border); + } + + .session-item:last-child { border-bottom: none; } + + .session-row { + display: flex; + align-items: center; + gap: var(--space-2); + padding: 6px var(--space-3); + cursor: pointer; + transition: background var(--transition-fast); + user-select: none; + } + + .session-row:hover { background: var(--bg-elevated); } + + .p-icon { + flex-shrink: 0; + width: 18px; + height: 18px; + display: flex; + align-items: center; + justify-content: center; + } + + .p-icon.discord { color: #5865F2; } + .p-icon.telegram { color: #26A5E4; } + .p-icon.whatsapp { color: #25D366; } + .p-icon.unknown { color: var(--text-muted); } + + .s-name { + font-size: var(--text-xs); + font-weight: 500; + color: var(--text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .s-tag { + font-size: 9px; + font-weight: 600; + padding: 0 4px; + border-radius: 3px; + flex-shrink: 0; + } + + .s-tag.grup { background: rgba(37, 211, 102, 0.12); color: #25D366; } + .s-tag.dm { background: rgba(156, 163, 175, 0.12); color: var(--text-muted); } + + .s-stats { + flex: 1; + display: flex; + gap: var(--space-2); + font-size: 10px; + color: var(--text-muted); + justify-content: flex-end; + white-space: nowrap; + font-family: var(--font-mono); + } + + .s-time { + font-size: 10px; + color: var(--text-muted); + white-space: nowrap; + flex-shrink: 0; + } + + .s-chevron { + color: var(--text-muted); + transition: transform var(--transition-fast); + flex-shrink: 0; + width: 12px; + height: 12px; + } + + .s-chevron.open { transform: rotate(180deg); } + + .session-details { + display: none; + padding: 4px var(--space-3) var(--space-3); + padding-left: calc(18px + var(--space-2) + var(--space-3)); + } + + .session-details.open { display: block; } + + .s-detail-row { + display: flex; + gap: var(--space-3); + font-size: 10px; + color: var(--text-muted); + margin-bottom: 6px; + font-family: var(--font-mono); + flex-wrap: wrap; + } + + .s-detail-row span { white-space: nowrap; } + + /* Conversation messages */ + .s-messages { + max-height: 300px; + overflow-y: auto; + border: 1px solid var(--border); + border-radius: var(--radius-sm); + background: var(--bg-elevated); + font-size: 11px; + line-height: 1.5; + } + + .s-msg { + padding: 4px 8px; + border-bottom: 1px solid var(--border); + } + + .s-msg:last-child { border-bottom: none; } + .s-msg.user { color: var(--text-secondary); } + .s-msg.assistant { color: var(--accent); } + + .s-msg-role { + font-size: 9px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; + margin-right: 6px; + } + + .s-msg.user .s-msg-role { color: var(--warning); } + .s-msg.assistant .s-msg-role { color: var(--accent); } + + .s-msg-text { + white-space: pre-wrap; + word-break: break-word; + } + + .s-loading { + padding: 8px; + text-align: center; + color: var(--text-muted); + font-size: 10px; + } + + .s-actions { + display: flex; + justify-content: flex-end; + margin-top: 6px; + } + + .s-actions .btn { + font-size: 10px; + padding: 2px 8px; + } + /* Log viewer */ .log-card { background: var(--bg-surface); @@ -361,13 +504,7 @@ .check-name { min-width: auto; } - .sessions-table { - font-size: var(--text-xs); - } - .sessions-table th:nth-child(3), - .sessions-table td:nth-child(3) { - display: none; - } + .s-stats { display: none; } } @@ -421,83 +558,102 @@ -
-
+
+

Services

+
-
-
Loading...
+
+
+
Loading...
+
-
-
+
+

Sessions

- +
+ + + +
-
-
-
Loading...
+
+
+
+
Loading...
+
-
-
+