854854 .flows-saved-item .flow-delete : hover { opacity : 1 ; }
855855
856856 /* Scheduler view */
857+ /* Logs view */
858+ .logs-view { display : flex; flex-direction : column; flex : 1 ; overflow : hidden; }
859+ .logs-view .hidden { display : none; }
860+ .logs-toolbar {
861+ display : flex; align-items : center; justify-content : space-between; gap : 8px ;
862+ padding : 8px 12px ; background : # 161b22 ; border-bottom : 1px solid # 21262d ; flex-shrink : 0 ;
863+ }
864+ .logs-filters { display : flex; gap : 6px ; align-items : center; flex : 1 ; min-width : 0 ; }
865+ .logs-filters select , .logs-filters input {
866+ padding : 5px 8px ; background : # 0d1117 ; border : 1px solid # 30363d ; border-radius : 4px ;
867+ color : # c9d1d9 ; font-size : 11px ; font-family : inherit; outline : none;
868+ }
869+ .logs-filters select { min-width : 100px ; }
870+ .logs-filters input { flex : 1 ; min-width : 120px ; }
871+ .logs-filters select : focus , .logs-filters input : focus { border-color : # 58a6ff ; }
872+ .logs-actions { display : flex; gap : 8px ; align-items : center; flex-shrink : 0 ; }
873+ .logs-actions label { font-size : 11px ; color : # 8b949e ; display : flex; align-items : center; gap : 4px ; cursor : pointer; }
874+ .logs-actions button {
875+ padding : 4px 10px ; background : # 21262d ; border : 1px solid # 30363d ; border-radius : 4px ;
876+ color : # c9d1d9 ; font-size : 11px ; cursor : pointer; font-family : inherit;
877+ }
878+ .logs-actions button : hover { background : # 30363d ; }
879+ .logs-output {
880+ flex : 1 ; overflow-y : auto; padding : 0 ; font-family : 'IBM Plex Mono' , monospace;
881+ font-size : 12px ; line-height : 1.6 ; background : # 0d1117 ;
882+ }
883+ .log-entry { display : flex; gap : 8px ; padding : 1px 10px ; border-bottom : 1px solid # 161b2200 ; }
884+ .log-entry : hover { background : # 161b22 ; }
885+ .log-ts { color : # 484f58 ; min-width : 80px ; flex-shrink : 0 ; white-space : nowrap; }
886+ .log-src {
887+ display : inline-block; min-width : 72px ; text-align : center; font-size : 10px ; font-weight : 600 ;
888+ padding : 0 6px ; border-radius : 3px ; flex-shrink : 0 ; line-height : 1.6 ;
889+ }
890+ .log-src-voice { color : # 58a6ff ; background : rgba (88 , 166 , 255 , 0.1 ); }
891+ .log-src-telegram { color : # 2188ff ; background : rgba (33 , 136 , 255 , 0.1 ); }
892+ .log-src-whatsapp { color : # 3fb950 ; background : rgba (63 , 185 , 80 , 0.1 ); }
893+ .log-src-triggers { color : # d29922 ; background : rgba (210 , 153 , 34 , 0.1 ); }
894+ .log-src-sdk { color : # bc8cff ; background : rgba (188 , 140 , 255 , 0.1 ); }
895+ .log-src-scheduler { color : # f0883e ; background : rgba (240 , 136 , 62 , 0.1 ); }
896+ .log-src-system { color : # 8b949e ; background : rgba (139 , 148 , 158 , 0.1 ); }
897+ .log-msg { flex : 1 ; white-space : pre-wrap; word-break : break-all; }
898+ .log-msg .log-error { color : # f85149 ; }
899+ .log-msg .log-warn { color : # d29922 ; }
900+ .log-msg .log-info { color : # c9d1d9 ; }
901+
857902 .scheduler-view { display : flex; flex-direction : column; flex : 1 ; overflow : hidden; }
858903 .scheduler-view .hidden { display : none; }
859904 .scheduler-content { flex : 1 ; overflow-y : auto; padding : 16px 20px ; display : flex; flex-direction : column; gap : 16px ; }
10431088 .comms-view { flex-direction : column; overflow-y : auto; }
10441089 .comms-view > div { width : 100% !important ; min-width : 0 !important ; max-width : none !important ; border-right : none !important ; }
10451090
1091+ /* ── Logs — stack toolbar ── */
1092+ .logs-toolbar { flex-direction : column; gap : 6px ; }
1093+ .logs-filters { flex-wrap : wrap; }
1094+
10461095 /* ── Settings — already responsive via max-width ── */
10471096 .settings-view { padding : 12px ; }
10481097 }
@@ -1064,6 +1113,7 @@ <h1>Gitclaw: {{AGENT_NAME}}</h1>
10641113 < button class ="view-tab " id ="tabComms " onclick ="switchView('comms') "> Communication</ button >
10651114 < button class ="view-tab " id ="tabFlows " onclick ="switchView('flows') "> SkillFlows</ button >
10661115 < button class ="view-tab " id ="tabScheduler " onclick ="switchView('scheduler') "> Scheduler</ button >
1116+ < button class ="view-tab " id ="tabLogs " onclick ="switchView('logs') "> Logs</ button >
10671117 < button class ="view-tab " id ="tabSettings " onclick ="switchView('settings') "> Settings</ button >
10681118 </ div >
10691119 </ div >
@@ -1413,6 +1463,34 @@ <h3 style="margin:0;color:#8b949e;font-size:12px;text-transform:uppercase;letter
14131463 < div id ="schedulesList "> </ div >
14141464 </ div >
14151465 </ div >
1466+ < div class ="logs-view hidden " id ="logsView ">
1467+ < div class ="logs-toolbar ">
1468+ < div class ="logs-filters ">
1469+ < select id ="logSourceFilter " onchange ="applyLogFilters() ">
1470+ < option value =""> All Sources</ option >
1471+ < option value ="voice "> Voice</ option >
1472+ < option value ="telegram "> Telegram</ option >
1473+ < option value ="whatsapp "> WhatsApp</ option >
1474+ < option value ="triggers "> Triggers</ option >
1475+ < option value ="sdk "> SDK</ option >
1476+ < option value ="scheduler "> Scheduler</ option >
1477+ < option value ="system "> System</ option >
1478+ </ select >
1479+ < select id ="logLevelFilter " onchange ="applyLogFilters() ">
1480+ < option value =""> All Levels</ option >
1481+ < option value ="info "> Info</ option >
1482+ < option value ="warn "> Warn</ option >
1483+ < option value ="error "> Error</ option >
1484+ </ select >
1485+ < input id ="logSearchInput " type ="text " placeholder ="Search logs... " oninput ="applyLogFilters() ">
1486+ </ div >
1487+ < div class ="logs-actions ">
1488+ < label class ="logs-autoscroll-label "> < input type ="checkbox " id ="logAutoScroll " checked > Auto-scroll</ label >
1489+ < button onclick ="clearLogView() "> Clear</ button >
1490+ </ div >
1491+ </ div >
1492+ < div class ="logs-output " id ="logsOutput "> </ div >
1493+ </ div >
14161494 < div class ="settings-view hidden " id ="settingsView ">
14171495 < div style ="max-width:560px;width:100%;margin:0 auto; ">
14181496 < h3 style ="margin:0 0 16px;color:#e6edf3;font-size:16px; "> Settings</ h3 >
@@ -1468,7 +1546,7 @@ <h3 style="margin:0 0 16px;color:#e6edf3;font-size:16px;">Settings</h3>
14681546< script >
14691547var hasComposio = { { HAS_COMPOSIO } } ;
14701548if ( ! hasComposio ) { document . getElementById ( 'tabIntegrations' ) . style . display = 'none' ; }
1471- let currentView = 'chat' , filesLoaded = false , integrationsLoaded = false , commsLoaded = false , skillsLoaded = false , flowsLoaded = false , schedulerLoaded = false , settingsLoaded = false ;
1549+ let currentView = 'chat' , filesLoaded = false , integrationsLoaded = false , commsLoaded = false , skillsLoaded = false , flowsLoaded = false , schedulerLoaded = false , logsLoaded = false , settingsLoaded = false ;
14721550function switchView ( v ) {
14731551 currentView = v ;
14741552 document . getElementById ( 'chatView' ) . classList . toggle ( 'hidden' , v !== 'chat' ) ;
@@ -1477,19 +1555,22 @@ <h3 style="margin:0 0 16px;color:#e6edf3;font-size:16px;">Settings</h3>
14771555 document . getElementById ( 'skillsView' ) . classList . toggle ( 'hidden' , v !== 'skills' ) ;
14781556 document . getElementById ( 'flowsView' ) . classList . toggle ( 'hidden' , v !== 'flows' ) ;
14791557 document . getElementById ( 'schedulerView' ) . classList . toggle ( 'hidden' , v !== 'scheduler' ) ;
1558+ document . getElementById ( 'logsView' ) . classList . toggle ( 'hidden' , v !== 'logs' ) ;
14801559 document . getElementById ( 'settingsView' ) . classList . toggle ( 'hidden' , v !== 'settings' ) ;
14811560 document . getElementById ( 'tabChat' ) . classList . toggle ( 'active' , v === 'chat' ) ;
14821561 document . getElementById ( 'tabIntegrations' ) . classList . toggle ( 'active' , v === 'integrations' ) ;
14831562 document . getElementById ( 'tabComms' ) . classList . toggle ( 'active' , v === 'comms' ) ;
14841563 document . getElementById ( 'tabSkills' ) . classList . toggle ( 'active' , v === 'skills' ) ;
14851564 document . getElementById ( 'tabFlows' ) . classList . toggle ( 'active' , v === 'flows' ) ;
14861565 document . getElementById ( 'tabScheduler' ) . classList . toggle ( 'active' , v === 'scheduler' ) ;
1566+ document . getElementById ( 'tabLogs' ) . classList . toggle ( 'active' , v === 'logs' ) ;
14871567 document . getElementById ( 'tabSettings' ) . classList . toggle ( 'active' , v === 'settings' ) ;
14881568 if ( v === 'integrations' && ! integrationsLoaded ) { loadToolkits ( ) ; integrationsLoaded = true ; }
14891569 if ( v === 'comms' && ! commsLoaded ) { loadTelegramStatus ( ) ; loadWhatsAppStatus ( ) ; loadPhoneWebhookUrl ( ) ; commsLoaded = true ; }
14901570 if ( v === 'skills' && ! skillsLoaded ) { document . getElementById ( 'skillsFrame' ) . src = '/api/skills-mp/proxy?path=/' ; skillsLoaded = true ; }
14911571 if ( v === 'flows' ) { loadFlowSkills ( ) ; loadSavedFlows ( ) ; flowsLoaded = true ; }
14921572 if ( v === 'scheduler' && ! schedulerLoaded ) { loadSchedules ( ) ; schedulerLoaded = true ; }
1573+ if ( v === 'logs' && ! logsLoaded ) { loadLogs ( ) ; logsLoaded = true ; }
14931574 if ( v === 'settings' && ! settingsLoaded ) { loadSettings ( ) ; settingsLoaded = true ; }
14941575}
14951576// Skills MP install handler
@@ -2038,6 +2119,9 @@ <h3 style="margin:0 0 16px;color:#e6edf3;font-size:16px;">Settings</h3>
20382119 setStatus ( 'Connected' , 'connected' ) ;
20392120 if ( filesLoaded ) loadFileTree ( ) ;
20402121 refreshFileTree ( ) ; break ;
2122+ case 'log_entry' :
2123+ appendLogEntry ( m . entry ) ;
2124+ break ;
20412125 case 'memory_saving' :
20422126 if ( m . status === 'start' ) {
20432127 var msEl = document . createElement ( 'div' ) ; msEl . className = 'conv-msg memory-saving' ; msEl . id = 'memory-saving-indicator' ;
@@ -2788,6 +2872,83 @@ <h3 style="margin:0 0 16px;color:#e6edf3;font-size:16px;">Settings</h3>
27882872 { value :'deepseek:deepseek-chat' , label :'DeepSeek Chat' } ,
27892873 { value :'' , label :'Custom (enter below)' }
27902874] ;
2875+ // ── Logs viewer ─────────────────────────────────────────────────────
2876+ var logEntries = [ ] ;
2877+ var logMaxId = 0 ;
2878+ var LOG_CLIENT_CAP = 5000 ;
2879+
2880+ function loadLogs ( ) {
2881+ fetch ( '/api/logs' ) . then ( function ( r ) { return r . json ( ) ; } ) . then ( function ( d ) {
2882+ if ( d . entries && d . entries . length ) {
2883+ logEntries = d . entries ;
2884+ logMaxId = d . entries [ d . entries . length - 1 ] . id ;
2885+ renderAllLogs ( ) ;
2886+ }
2887+ } ) . catch ( function ( ) { } ) ;
2888+ }
2889+
2890+ function appendLogEntry ( entry ) {
2891+ logEntries . push ( entry ) ;
2892+ if ( entry . id > logMaxId ) logMaxId = entry . id ;
2893+ if ( logEntries . length > LOG_CLIENT_CAP ) logEntries = logEntries . slice ( logEntries . length - LOG_CLIENT_CAP ) ;
2894+ if ( matchesLogFilter ( entry ) ) {
2895+ var el = renderLogEntry ( entry ) ;
2896+ var out = document . getElementById ( 'logsOutput' ) ;
2897+ out . appendChild ( el ) ;
2898+ if ( document . getElementById ( 'logAutoScroll' ) . checked ) out . scrollTop = out . scrollHeight ;
2899+ }
2900+ }
2901+
2902+ function renderLogEntry ( entry ) {
2903+ var row = document . createElement ( 'div' ) ; row . className = 'log-entry' ;
2904+ var ts = document . createElement ( 'span' ) ; ts . className = 'log-ts' ;
2905+ var d = new Date ( entry . ts ) ;
2906+ ts . textContent = d . toLocaleTimeString ( 'en-US' , { hour12 :false , hour :'2-digit' , minute :'2-digit' , second :'2-digit' } ) + '.' + String ( d . getMilliseconds ( ) ) . padStart ( 3 , '0' ) ;
2907+ var src = document . createElement ( 'span' ) ;
2908+ src . className = 'log-src log-src-' + ( entry . source || 'system' ) ;
2909+ src . textContent = entry . source || 'system' ;
2910+ var msg = document . createElement ( 'span' ) ;
2911+ msg . className = 'log-msg log-' + entry . level ;
2912+ msg . textContent = entry . message ;
2913+ row . appendChild ( ts ) ; row . appendChild ( src ) ; row . appendChild ( msg ) ;
2914+ return row ;
2915+ }
2916+
2917+ function matchesLogFilter ( entry ) {
2918+ var sf = document . getElementById ( 'logSourceFilter' ) . value ;
2919+ var lf = document . getElementById ( 'logLevelFilter' ) . value ;
2920+ var q = ( document . getElementById ( 'logSearchInput' ) . value || '' ) . toLowerCase ( ) ;
2921+ if ( sf && entry . source !== sf ) return false ;
2922+ if ( lf && entry . level !== lf ) return false ;
2923+ if ( q && entry . message . toLowerCase ( ) . indexOf ( q ) === - 1 ) return false ;
2924+ return true ;
2925+ }
2926+
2927+ function applyLogFilters ( ) {
2928+ var out = document . getElementById ( 'logsOutput' ) ; out . innerHTML = '' ;
2929+ for ( var i = 0 ; i < logEntries . length ; i ++ ) {
2930+ if ( matchesLogFilter ( logEntries [ i ] ) ) {
2931+ out . appendChild ( renderLogEntry ( logEntries [ i ] ) ) ;
2932+ }
2933+ }
2934+ if ( document . getElementById ( 'logAutoScroll' ) . checked ) out . scrollTop = out . scrollHeight ;
2935+ }
2936+
2937+ function clearLogView ( ) {
2938+ logEntries = [ ] ;
2939+ document . getElementById ( 'logsOutput' ) . innerHTML = '' ;
2940+ }
2941+
2942+ function renderAllLogs ( ) {
2943+ var out = document . getElementById ( 'logsOutput' ) ; out . innerHTML = '' ;
2944+ for ( var i = 0 ; i < logEntries . length ; i ++ ) {
2945+ if ( matchesLogFilter ( logEntries [ i ] ) ) {
2946+ out . appendChild ( renderLogEntry ( logEntries [ i ] ) ) ;
2947+ }
2948+ }
2949+ if ( document . getElementById ( 'logAutoScroll' ) . checked ) out . scrollTop = out . scrollHeight ;
2950+ }
2951+
27912952function loadSettings ( ) {
27922953 fetch ( '/api/settings' ) . then ( function ( r ) { return r . json ( ) ; } ) . then ( function ( d ) {
27932954 var sel = document . getElementById ( 'settingsModel' ) ;
0 commit comments