@nogataka/smart-edit 0.0.14

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 (186) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +244 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +7 -0
  5. package/dist/devtools/generate_prompt_factory.d.ts +5 -0
  6. package/dist/devtools/generate_prompt_factory.js +114 -0
  7. package/dist/index.d.ts +34 -0
  8. package/dist/index.js +34 -0
  9. package/dist/interprompt/index.d.ts +2 -0
  10. package/dist/interprompt/index.js +1 -0
  11. package/dist/interprompt/jinja_template.d.ts +10 -0
  12. package/dist/interprompt/jinja_template.js +174 -0
  13. package/dist/interprompt/multilang_prompt.d.ts +54 -0
  14. package/dist/interprompt/multilang_prompt.js +302 -0
  15. package/dist/interprompt/prompt_factory.d.ts +16 -0
  16. package/dist/interprompt/prompt_factory.js +189 -0
  17. package/dist/interprompt/util/class_decorators.d.ts +1 -0
  18. package/dist/interprompt/util/class_decorators.js +1 -0
  19. package/dist/interprompt/util/index.d.ts +1 -0
  20. package/dist/interprompt/util/index.js +1 -0
  21. package/dist/serena/agent.d.ts +118 -0
  22. package/dist/serena/agent.js +675 -0
  23. package/dist/serena/agno.d.ts +111 -0
  24. package/dist/serena/agno.js +278 -0
  25. package/dist/serena/analytics.d.ts +24 -0
  26. package/dist/serena/analytics.js +119 -0
  27. package/dist/serena/cli.d.ts +9 -0
  28. package/dist/serena/cli.js +731 -0
  29. package/dist/serena/code_editor.d.ts +42 -0
  30. package/dist/serena/code_editor.js +239 -0
  31. package/dist/serena/config/context_mode.d.ts +41 -0
  32. package/dist/serena/config/context_mode.js +239 -0
  33. package/dist/serena/config/serena_config.d.ts +134 -0
  34. package/dist/serena/config/serena_config.js +718 -0
  35. package/dist/serena/constants.d.ts +18 -0
  36. package/dist/serena/constants.js +27 -0
  37. package/dist/serena/dashboard.d.ts +55 -0
  38. package/dist/serena/dashboard.js +472 -0
  39. package/dist/serena/generated/generated_prompt_factory.d.ts +27 -0
  40. package/dist/serena/generated/generated_prompt_factory.js +42 -0
  41. package/dist/serena/gui_log_viewer.d.ts +41 -0
  42. package/dist/serena/gui_log_viewer.js +436 -0
  43. package/dist/serena/mcp.d.ts +118 -0
  44. package/dist/serena/mcp.js +904 -0
  45. package/dist/serena/project.d.ts +62 -0
  46. package/dist/serena/project.js +321 -0
  47. package/dist/serena/prompt_factory.d.ts +20 -0
  48. package/dist/serena/prompt_factory.js +42 -0
  49. package/dist/serena/resources/config/contexts/agent.yml +8 -0
  50. package/dist/serena/resources/config/contexts/chatgpt.yml +28 -0
  51. package/dist/serena/resources/config/contexts/codex.yml +27 -0
  52. package/dist/serena/resources/config/contexts/context.template.yml +11 -0
  53. package/dist/serena/resources/config/contexts/desktop-app.yml +17 -0
  54. package/dist/serena/resources/config/contexts/ide-assistant.yml +26 -0
  55. package/dist/serena/resources/config/contexts/oaicompat-agent.yml +8 -0
  56. package/dist/serena/resources/config/internal_modes/jetbrains.yml +15 -0
  57. package/dist/serena/resources/config/modes/editing.yml +112 -0
  58. package/dist/serena/resources/config/modes/interactive.yml +11 -0
  59. package/dist/serena/resources/config/modes/mode.template.yml +7 -0
  60. package/dist/serena/resources/config/modes/no-onboarding.yml +8 -0
  61. package/dist/serena/resources/config/modes/onboarding.yml +16 -0
  62. package/dist/serena/resources/config/modes/one-shot.yml +15 -0
  63. package/dist/serena/resources/config/modes/planning.yml +15 -0
  64. package/dist/serena/resources/config/prompt_templates/simple_tool_outputs.yml +75 -0
  65. package/dist/serena/resources/config/prompt_templates/system_prompt.yml +66 -0
  66. package/dist/serena/resources/dashboard/dashboard.js +815 -0
  67. package/dist/serena/resources/dashboard/index.html +314 -0
  68. package/dist/serena/resources/dashboard/jquery.min.js +3 -0
  69. package/dist/serena/resources/dashboard/serena-icon-16.png +0 -0
  70. package/dist/serena/resources/dashboard/serena-icon-32.png +0 -0
  71. package/dist/serena/resources/dashboard/serena-icon-48.png +0 -0
  72. package/dist/serena/resources/dashboard/serena-logs-dark-mode.png +0 -0
  73. package/dist/serena/resources/dashboard/serena-logs.png +0 -0
  74. package/dist/serena/resources/project.template.yml +67 -0
  75. package/dist/serena/resources/serena_config.template.yml +85 -0
  76. package/dist/serena/symbol.d.ts +199 -0
  77. package/dist/serena/symbol.js +616 -0
  78. package/dist/serena/text_utils.d.ts +51 -0
  79. package/dist/serena/text_utils.js +267 -0
  80. package/dist/serena/tools/cmd_tools.d.ts +31 -0
  81. package/dist/serena/tools/cmd_tools.js +48 -0
  82. package/dist/serena/tools/config_tools.d.ts +53 -0
  83. package/dist/serena/tools/config_tools.js +176 -0
  84. package/dist/serena/tools/file_tools.d.ts +231 -0
  85. package/dist/serena/tools/file_tools.js +511 -0
  86. package/dist/serena/tools/index.d.ts +7 -0
  87. package/dist/serena/tools/index.js +7 -0
  88. package/dist/serena/tools/memory_tools.d.ts +60 -0
  89. package/dist/serena/tools/memory_tools.js +135 -0
  90. package/dist/serena/tools/symbol_tools.d.ts +165 -0
  91. package/dist/serena/tools/symbol_tools.js +362 -0
  92. package/dist/serena/tools/tools_base.d.ts +162 -0
  93. package/dist/serena/tools/tools_base.js +378 -0
  94. package/dist/serena/tools/workflow_tools.d.ts +35 -0
  95. package/dist/serena/tools/workflow_tools.js +161 -0
  96. package/dist/serena/util/class_decorators.d.ts +7 -0
  97. package/dist/serena/util/class_decorators.js +37 -0
  98. package/dist/serena/util/exception.d.ts +8 -0
  99. package/dist/serena/util/exception.js +53 -0
  100. package/dist/serena/util/file_system.d.ts +30 -0
  101. package/dist/serena/util/file_system.js +352 -0
  102. package/dist/serena/util/general.d.ts +11 -0
  103. package/dist/serena/util/general.js +42 -0
  104. package/dist/serena/util/git.d.ts +11 -0
  105. package/dist/serena/util/git.js +37 -0
  106. package/dist/serena/util/inspection.d.ts +45 -0
  107. package/dist/serena/util/inspection.js +221 -0
  108. package/dist/serena/util/logging.d.ts +46 -0
  109. package/dist/serena/util/logging.js +205 -0
  110. package/dist/serena/util/shell.d.ts +21 -0
  111. package/dist/serena/util/shell.js +95 -0
  112. package/dist/serena/util/thread.d.ts +23 -0
  113. package/dist/serena/util/thread.js +88 -0
  114. package/dist/serena/version.d.ts +1 -0
  115. package/dist/serena/version.js +23 -0
  116. package/dist/solidlsp/language_servers/autoload.d.ts +23 -0
  117. package/dist/solidlsp/language_servers/autoload.js +25 -0
  118. package/dist/solidlsp/language_servers/bash_language_server.d.ts +10 -0
  119. package/dist/solidlsp/language_servers/bash_language_server.js +64 -0
  120. package/dist/solidlsp/language_servers/clangd_language_server.d.ts +13 -0
  121. package/dist/solidlsp/language_servers/clangd_language_server.js +110 -0
  122. package/dist/solidlsp/language_servers/clojure_lsp.d.ts +13 -0
  123. package/dist/solidlsp/language_servers/clojure_lsp.js +137 -0
  124. package/dist/solidlsp/language_servers/common.d.ts +41 -0
  125. package/dist/solidlsp/language_servers/common.js +365 -0
  126. package/dist/solidlsp/language_servers/csharp_language_server.d.ts +21 -0
  127. package/dist/solidlsp/language_servers/csharp_language_server.js +694 -0
  128. package/dist/solidlsp/language_servers/dart_language_server.d.ts +10 -0
  129. package/dist/solidlsp/language_servers/dart_language_server.js +122 -0
  130. package/dist/solidlsp/language_servers/eclipse_jdtls.d.ts +24 -0
  131. package/dist/solidlsp/language_servers/eclipse_jdtls.js +671 -0
  132. package/dist/solidlsp/language_servers/erlang_language_server.d.ts +22 -0
  133. package/dist/solidlsp/language_servers/erlang_language_server.js +327 -0
  134. package/dist/solidlsp/language_servers/gopls.d.ts +12 -0
  135. package/dist/solidlsp/language_servers/gopls.js +59 -0
  136. package/dist/solidlsp/language_servers/intelephense.d.ts +13 -0
  137. package/dist/solidlsp/language_servers/intelephense.js +121 -0
  138. package/dist/solidlsp/language_servers/jedi_server.d.ts +18 -0
  139. package/dist/solidlsp/language_servers/jedi_server.js +234 -0
  140. package/dist/solidlsp/language_servers/kotlin_language_server.d.ts +19 -0
  141. package/dist/solidlsp/language_servers/kotlin_language_server.js +474 -0
  142. package/dist/solidlsp/language_servers/lua_ls.d.ts +18 -0
  143. package/dist/solidlsp/language_servers/lua_ls.js +319 -0
  144. package/dist/solidlsp/language_servers/nixd_language_server.d.ts +17 -0
  145. package/dist/solidlsp/language_servers/nixd_language_server.js +341 -0
  146. package/dist/solidlsp/language_servers/pyright_server.d.ts +19 -0
  147. package/dist/solidlsp/language_servers/pyright_server.js +180 -0
  148. package/dist/solidlsp/language_servers/r_language_server.d.ts +19 -0
  149. package/dist/solidlsp/language_servers/r_language_server.js +184 -0
  150. package/dist/solidlsp/language_servers/ruby_common.d.ts +10 -0
  151. package/dist/solidlsp/language_servers/ruby_common.js +136 -0
  152. package/dist/solidlsp/language_servers/ruby_lsp.d.ts +18 -0
  153. package/dist/solidlsp/language_servers/ruby_lsp.js +230 -0
  154. package/dist/solidlsp/language_servers/rust_analyzer.d.ts +13 -0
  155. package/dist/solidlsp/language_servers/rust_analyzer.js +96 -0
  156. package/dist/solidlsp/language_servers/solargraph.d.ts +18 -0
  157. package/dist/solidlsp/language_servers/solargraph.js +208 -0
  158. package/dist/solidlsp/language_servers/sourcekit_lsp.d.ts +24 -0
  159. package/dist/solidlsp/language_servers/sourcekit_lsp.js +449 -0
  160. package/dist/solidlsp/language_servers/terraform_ls.d.ts +13 -0
  161. package/dist/solidlsp/language_servers/terraform_ls.js +139 -0
  162. package/dist/solidlsp/language_servers/typescript_language_server.d.ts +20 -0
  163. package/dist/solidlsp/language_servers/typescript_language_server.js +237 -0
  164. package/dist/solidlsp/language_servers/vts_language_server.d.ts +13 -0
  165. package/dist/solidlsp/language_servers/vts_language_server.js +121 -0
  166. package/dist/solidlsp/language_servers/zls.d.ts +20 -0
  167. package/dist/solidlsp/language_servers/zls.js +254 -0
  168. package/dist/solidlsp/ls.d.ts +197 -0
  169. package/dist/solidlsp/ls.js +507 -0
  170. package/dist/solidlsp/ls_config.d.ts +43 -0
  171. package/dist/solidlsp/ls_config.js +157 -0
  172. package/dist/solidlsp/ls_exceptions.d.ts +5 -0
  173. package/dist/solidlsp/ls_exceptions.js +14 -0
  174. package/dist/solidlsp/ls_handler.d.ts +54 -0
  175. package/dist/solidlsp/ls_handler.js +406 -0
  176. package/dist/solidlsp/ls_request.d.ts +31 -0
  177. package/dist/solidlsp/ls_request.js +42 -0
  178. package/dist/solidlsp/ls_types.d.ts +7 -0
  179. package/dist/solidlsp/ls_types.js +8 -0
  180. package/dist/solidlsp/lsp_protocol_handler/server.d.ts +61 -0
  181. package/dist/solidlsp/lsp_protocol_handler/server.js +68 -0
  182. package/dist/solidlsp/util/subprocess_util.d.ts +6 -0
  183. package/dist/solidlsp/util/subprocess_util.js +11 -0
  184. package/dist/solidlsp/util/zip.d.ts +25 -0
  185. package/dist/solidlsp/util/zip.js +188 -0
  186. package/package.json +65 -0
@@ -0,0 +1,815 @@
1
+ /* eslint-disable */
2
+
3
+ class LogMessage {
4
+ constructor(message, toolNames) {
5
+ message = this.escapeHtml(message);
6
+ const logLevel = this.determineLogLevel(message);
7
+ const highlightedMessage = this.highlightToolNames(message, toolNames);
8
+ this.$elem = $('<div>').addClass('log-' + logLevel).html(highlightedMessage + '\n');
9
+ }
10
+
11
+ determineLogLevel(message) {
12
+ if (message.startsWith('DEBUG')) {
13
+ return 'debug';
14
+ } else if (message.startsWith('INFO')) {
15
+ return 'info';
16
+ } else if (message.startsWith('WARNING')) {
17
+ return 'warning';
18
+ } else if (message.startsWith('ERROR')) {
19
+ return 'error';
20
+ } else {
21
+ return 'default';
22
+ }
23
+ }
24
+
25
+ highlightToolNames(message, toolNames) {
26
+ let highlightedMessage = message;
27
+ toolNames.forEach(function(toolName) {
28
+ const regex = new RegExp('\\b' + toolName + '\\b', 'gi');
29
+ highlightedMessage = highlightedMessage.replace(regex, '<span class="tool-name">' + toolName + '</span>');
30
+ });
31
+ return highlightedMessage;
32
+ }
33
+
34
+ escapeHtml (convertString) {
35
+ if (typeof convertString !== 'string') return convertString;
36
+
37
+ const patterns = {
38
+ '<' : '&lt;',
39
+ '>' : '&gt;',
40
+ '&' : '&amp;',
41
+ '"' : '&quot;',
42
+ '\'' : '&#x27;',
43
+ '`' : '&#x60;'
44
+ };
45
+
46
+ return convertString.replace(/[<>&"'`]/g, match => patterns[match]);
47
+ };
48
+ }
49
+
50
+ class Dashboard {
51
+ constructor() {
52
+ let self = this;
53
+
54
+ this.toolNames = [];
55
+ this.currentMaxIdx = -1;
56
+ this.pollInterval = null;
57
+ this.failureCount = 0;
58
+ this.useStreaming = false;
59
+ this.eventSource = null;
60
+ this.$logContainer = $('#log-container');
61
+ this.$errorContainer = $('#error-container');
62
+ this.$loadButton = $('#load-logs');
63
+ this.$shutdownButton = $('#shutdown');
64
+ this.$toggleStats = $('#toggle-stats');
65
+ this.$statsSection = $('#stats-section');
66
+ this.$refreshStats = $('#refresh-stats');
67
+ this.$clearStats = $('#clear-stats');
68
+ this.$themeToggle = $('#theme-toggle');
69
+ this.$themeIcon = $('#theme-icon');
70
+ this.$themeText = $('#theme-text');
71
+ this.$toolList = $('#tool-list');
72
+
73
+ this.updateToolList();
74
+
75
+ this.countChart = null;
76
+ this.tokensChart = null;
77
+ this.inputChart = null;
78
+ this.outputChart = null;
79
+
80
+ // register event handlers
81
+ this.$loadButton.click(this.loadLogs.bind(this));
82
+ this.$shutdownButton.click(this.shutdown.bind(this));
83
+ this.$toggleStats.click(this.toggleStats.bind(this));
84
+ this.$refreshStats.click(this.loadStats.bind(this));
85
+ this.$clearStats.click(this.clearStats.bind(this));
86
+ this.$themeToggle.click(this.toggleTheme.bind(this));
87
+
88
+ // initialize theme
89
+ this.initializeTheme();
90
+
91
+ // initialize the application
92
+ this.loadToolNames().then(function() {
93
+ if (!self.setupLogStreaming()) {
94
+ self.loadLogs();
95
+ }
96
+ });
97
+ }
98
+
99
+ displayLogMessage(message) {
100
+ $('#log-container').append(new LogMessage(message, this.toolNames).$elem);
101
+ }
102
+
103
+ updateToolList() {
104
+ if (!this.$toolList) return;
105
+ if (!this.toolNames || this.toolNames.length === 0) {
106
+ this.$toolList.text('Tools: なし');
107
+ } else {
108
+ this.$toolList.text('Tools: ' + this.toolNames.join(', '));
109
+ }
110
+ }
111
+
112
+ loadToolNames() {
113
+ let self = this;
114
+ return $.ajax({
115
+ url: '/get_tool_names',
116
+ type: 'GET',
117
+ success: function(response) {
118
+ self.toolNames = response.tool_names || response.toolNames || [];
119
+ self.updateToolList();
120
+ console.log('Loaded tool names:', self.toolNames);
121
+ },
122
+ error: function(xhr, status, error) {
123
+ console.error('Error loading tool names:', error);
124
+ }
125
+ });
126
+ }
127
+
128
+ setupLogStreaming() {
129
+ if (!window.EventSource) {
130
+ return false;
131
+ }
132
+
133
+ try {
134
+ this.eventSource = new EventSource('/log_stream');
135
+ } catch (error) {
136
+ console.error('EventSource init failed:', error);
137
+ this.eventSource = null;
138
+ return false;
139
+ }
140
+
141
+ const source = this.eventSource;
142
+ let initialized = false;
143
+
144
+ source.addEventListener('open', () => {
145
+ this.useStreaming = true;
146
+ if (this.pollInterval) {
147
+ clearInterval(this.pollInterval);
148
+ this.pollInterval = null;
149
+ }
150
+ });
151
+
152
+ source.addEventListener('history', (event) => {
153
+ initialized = true;
154
+ this.useStreaming = true;
155
+ const payload = this.safeParse(event.data);
156
+ if (payload) {
157
+ this.applyLogHistory(payload);
158
+ }
159
+ });
160
+
161
+ source.addEventListener('log', (event) => {
162
+ const payload = this.safeParse(event.data);
163
+ if (payload) {
164
+ this.handleStreamedLog(payload);
165
+ }
166
+ });
167
+
168
+ source.addEventListener('toolNames', (event) => {
169
+ const payload = this.safeParse(event.data);
170
+ if (payload) {
171
+ this.toolNames = payload.toolNames || payload.tool_names || [];
172
+ this.updateToolList();
173
+ }
174
+ });
175
+
176
+ source.onerror = () => {
177
+ console.error('Log stream encountered an error, falling back to polling.');
178
+ source.close();
179
+ this.eventSource = null;
180
+ this.useStreaming = false;
181
+ if (this.pollInterval) {
182
+ clearInterval(this.pollInterval);
183
+ this.pollInterval = null;
184
+ }
185
+ if (!initialized) {
186
+ this.loadLogs();
187
+ } else {
188
+ this.startPeriodicPolling();
189
+ }
190
+ };
191
+
192
+ return true;
193
+ }
194
+
195
+ applyLogHistory(payload) {
196
+ const messages = payload.messages || [];
197
+ const maxIdx = typeof payload.max_idx === 'number' ? payload.max_idx : payload.maxIdx;
198
+ const activeProject = payload.active_project !== undefined ? payload.active_project : payload.activeProject;
199
+
200
+ this.$logContainer.empty();
201
+ if (messages.length === 0) {
202
+ this.$logContainer.html('<div class="loading">No log messages found.</div>');
203
+ this.currentMaxIdx = typeof maxIdx === 'number' ? maxIdx : -1;
204
+ } else {
205
+ messages.forEach((message) => this.displayLogMessage(message));
206
+ const logContainer = this.$logContainer[0];
207
+ if (logContainer) {
208
+ logContainer.scrollTop = logContainer.scrollHeight;
209
+ }
210
+ this.currentMaxIdx = typeof maxIdx === 'number' ? maxIdx : messages.length - 1;
211
+ }
212
+
213
+ if (activeProject !== undefined) {
214
+ this.updateTitle(activeProject);
215
+ }
216
+ }
217
+
218
+ handleStreamedLog(payload) {
219
+ if (!payload || !payload.message) {
220
+ return;
221
+ }
222
+
223
+ const logContainer = this.$logContainer[0];
224
+ let wasAtBottom = false;
225
+ if (logContainer) {
226
+ wasAtBottom = (logContainer.scrollTop + logContainer.clientHeight) >= (logContainer.scrollHeight - 10);
227
+ }
228
+
229
+ this.displayLogMessage(payload.message);
230
+
231
+ const idx = typeof payload.idx === 'number' ? payload.idx : this.currentMaxIdx + 1;
232
+ if (typeof idx === 'number') {
233
+ this.currentMaxIdx = Math.max(this.currentMaxIdx, idx);
234
+ }
235
+
236
+ if (payload.activeProject !== undefined) {
237
+ this.updateTitle(payload.activeProject);
238
+ }
239
+
240
+ if (wasAtBottom && logContainer) {
241
+ logContainer.scrollTop = logContainer.scrollHeight;
242
+ }
243
+ }
244
+
245
+ safeParse(data) {
246
+ try {
247
+ return JSON.parse(data);
248
+ } catch (error) {
249
+ console.error('Failed to parse dashboard event payload:', error);
250
+ return null;
251
+ }
252
+ }
253
+
254
+ updateTitle(activeProject) {
255
+ document.title = activeProject ? `${activeProject} – Serena Dashboard` : 'Serena Dashboard';
256
+ }
257
+
258
+ loadLogs() {
259
+ console.log("Loading logs");
260
+ let self = this;
261
+
262
+ // Disable button and show loading state
263
+ self.$loadButton.prop('disabled', true).text('Loading...');
264
+ self.$errorContainer.empty();
265
+
266
+ // Make API call
267
+ $.ajax({
268
+ url: '/get_log_messages',
269
+ type: 'POST',
270
+ contentType: 'application/json',
271
+ data: JSON.stringify({
272
+ start_idx: 0
273
+ }),
274
+ success: function(response) {
275
+ // Clear existing logs
276
+ self.$logContainer.empty();
277
+
278
+ // Update max_idx
279
+ self.currentMaxIdx = response.max_idx || -1;
280
+
281
+ // Display each log message
282
+ if (response.messages && response.messages.length > 0) {
283
+ response.messages.forEach(function(message) {
284
+ self.displayLogMessage(message);
285
+ });
286
+
287
+ // Auto-scroll to bottom
288
+ const logContainer = $('#log-container')[0];
289
+ logContainer.scrollTop = logContainer.scrollHeight;
290
+ } else {
291
+ $('#log-container').html('<div class="loading">No log messages found.</div>');
292
+ }
293
+
294
+ self.updateTitle(response.active_project);
295
+
296
+ // Start periodic polling for new logs when streaming is unavailable
297
+ if (!self.useStreaming) {
298
+ self.startPeriodicPolling();
299
+ }
300
+ },
301
+ error: function(xhr, status, error) {
302
+ console.error('Error loading logs:', error);
303
+ self.$errorContainer.html('<div class="error-message">Error loading logs: ' +
304
+ (xhr.responseJSON ? xhr.responseJSON.detail : error) + '</div>');
305
+ },
306
+ complete: function() {
307
+ // Re-enable button
308
+ self.$loadButton.prop('disabled', false).text('Reload Log');
309
+ }
310
+ });
311
+ }
312
+
313
+ pollForNewLogs() {
314
+ let self = this;
315
+ console.log("Polling logs", this.currentMaxIdx);
316
+ $.ajax({
317
+ url: '/get_log_messages',
318
+ type: 'POST',
319
+ contentType: 'application/json',
320
+ data: JSON.stringify({
321
+ start_idx: self.currentMaxIdx + 1
322
+ }),
323
+ success: function(response) {
324
+ self.failureCount = 0;
325
+ // Only append new messages if we have any
326
+ if (response.messages && response.messages.length > 0) {
327
+ let wasAtBottom = false;
328
+ const logContainer = $('#log-container')[0];
329
+
330
+ // Check if user was at the bottom before adding new logs
331
+ if (logContainer.scrollHeight > 0) {
332
+ wasAtBottom = (logContainer.scrollTop + logContainer.clientHeight) >= (logContainer.scrollHeight - 10);
333
+ }
334
+
335
+ // Append new messages
336
+ response.messages.forEach(function(message) {
337
+ self.displayLogMessage(message);
338
+ });
339
+
340
+ // Update max_idx
341
+ self.currentMaxIdx = response.max_idx || self.currentMaxIdx;
342
+
343
+ // Auto-scroll to bottom if user was already at bottom
344
+ if (wasAtBottom) {
345
+ logContainer.scrollTop = logContainer.scrollHeight;
346
+ }
347
+ } else {
348
+ // Update max_idx even if no new messages
349
+ self.currentMaxIdx = response.max_idx || self.currentMaxIdx;
350
+ }
351
+
352
+ // Update window title with active project
353
+ self.updateTitle(response.active_project);
354
+ },
355
+ error: function(xhr, status, error) {
356
+ console.error('Error polling for new logs:', error);
357
+ self.failureCount++;
358
+ if (self.failureCount >= 3) {
359
+ console.log('Server appears to be down, closing tab');
360
+ window.close();
361
+ }
362
+ }
363
+ });
364
+ }
365
+
366
+ startPeriodicPolling() {
367
+ if (this.useStreaming) {
368
+ if (this.pollInterval) {
369
+ clearInterval(this.pollInterval);
370
+ this.pollInterval = null;
371
+ }
372
+ return;
373
+ }
374
+ // Clear any existing interval
375
+ if (this.pollInterval) {
376
+ clearInterval(this.pollInterval);
377
+ }
378
+
379
+ // Start polling every second (1000ms)
380
+ this.pollInterval = setInterval(this.pollForNewLogs.bind(this), 1000);
381
+ }
382
+
383
+ toggleStats() {
384
+ if (this.$statsSection.is(':visible')) {
385
+ this.$statsSection.hide();
386
+ this.$toggleStats.text('Show Stats');
387
+ } else {
388
+ this.$statsSection.show();
389
+ this.$toggleStats.text('Hide Stats');
390
+ this.loadStats();
391
+ }
392
+ }
393
+
394
+ loadStats() {
395
+ let self = this;
396
+ $.when(
397
+ $.ajax({ url: '/get_tool_stats', type: 'GET' }),
398
+ $.ajax({ url: '/get_token_count_estimator_name', type: 'GET' })
399
+ ).done(function(statsResp, estimatorResp) {
400
+ const stats = statsResp[0].stats;
401
+ const tokenCountEstimatorName = estimatorResp[0].token_count_estimator_name;
402
+ self.displayStats(stats, tokenCountEstimatorName);
403
+ }).fail(function() {
404
+ console.error('Error loading stats or estimator name');
405
+ });
406
+ }
407
+
408
+
409
+ clearStats() {
410
+ let self = this;
411
+ $.ajax({
412
+ url: '/clear_tool_stats',
413
+ type: 'POST',
414
+ success: function() {
415
+ self.loadStats();
416
+ },
417
+ error: function(xhr, status, error) {
418
+ console.error('Error clearing stats:', error);
419
+ }
420
+ });
421
+ }
422
+
423
+ displayStats(stats, tokenCountEstimatorName) {
424
+ const names = Object.keys(stats);
425
+ // If no stats collected
426
+ if (names.length === 0) {
427
+ // hide summary, charts, estimator name
428
+ $('#stats-summary').hide();
429
+ $('#estimator-name').hide();
430
+ $('.charts-container').hide();
431
+ // show no-stats message
432
+ $('#no-stats-message').show();
433
+ return;
434
+ } else {
435
+ // Ensure everything is visible
436
+ $('#estimator-name').show();
437
+ $('#stats-summary').show();
438
+ $('.charts-container').show();
439
+ $('#no-stats-message').hide();
440
+ }
441
+
442
+ $('#estimator-name').html(`<strong>Token count estimator:</strong> ${tokenCountEstimatorName}`);
443
+
444
+ const counts = names.map(n => stats[n].num_times_called);
445
+ const inputTokens = names.map(n => stats[n].input_tokens);
446
+ const outputTokens = names.map(n => stats[n].output_tokens);
447
+ const totalTokens = names.map(n => stats[n].input_tokens + stats[n].output_tokens);
448
+
449
+ // Calculate totals for summary table
450
+ const totalCalls = counts.reduce((sum, count) => sum + count, 0);
451
+ const totalInputTokens = inputTokens.reduce((sum, tokens) => sum + tokens, 0);
452
+ const totalOutputTokens = outputTokens.reduce((sum, tokens) => sum + tokens, 0);
453
+
454
+ // Generate consistent colors for tools
455
+ const colors = this.generateColors(names.length);
456
+
457
+ const countCtx = document.getElementById('count-chart');
458
+ const tokensCtx = document.getElementById('tokens-chart');
459
+ const inputCtx = document.getElementById('input-chart');
460
+ const outputCtx = document.getElementById('output-chart');
461
+
462
+ if (this.countChart) this.countChart.destroy();
463
+ if (this.tokensChart) this.tokensChart.destroy();
464
+ if (this.inputChart) this.inputChart.destroy();
465
+ if (this.outputChart) this.outputChart.destroy();
466
+
467
+ // Update summary table
468
+ this.updateSummaryTable(totalCalls, totalInputTokens, totalOutputTokens);
469
+
470
+ // Register datalabels plugin
471
+ Chart.register(ChartDataLabels);
472
+
473
+ // Get theme-aware colors
474
+ const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
475
+ const textColor = isDark ? '#ffffff' : '#000000';
476
+ const gridColor = isDark ? '#444' : '#ddd';
477
+
478
+ // Tool calls pie chart
479
+ this.countChart = new Chart(countCtx, {
480
+ type: 'pie',
481
+ data: {
482
+ labels: names,
483
+ datasets: [{
484
+ data: counts,
485
+ backgroundColor: colors
486
+ }]
487
+ },
488
+ options: {
489
+ plugins: {
490
+ legend: {
491
+ display: true,
492
+ labels: {
493
+ color: textColor
494
+ }
495
+ },
496
+ datalabels: {
497
+ display: true,
498
+ color: 'white',
499
+ font: { weight: 'bold' },
500
+ formatter: (value) => value
501
+ }
502
+ }
503
+ }
504
+ });
505
+
506
+ // Input tokens pie chart
507
+ this.inputChart = new Chart(inputCtx, {
508
+ type: 'pie',
509
+ data: {
510
+ labels: names,
511
+ datasets: [{
512
+ data: inputTokens,
513
+ backgroundColor: colors
514
+ }]
515
+ },
516
+ options: {
517
+ plugins: {
518
+ legend: {
519
+ display: true,
520
+ labels: {
521
+ color: textColor
522
+ }
523
+ },
524
+ datalabels: {
525
+ display: true,
526
+ color: 'white',
527
+ font: { weight: 'bold' },
528
+ formatter: (value) => value
529
+ }
530
+ }
531
+ }
532
+ });
533
+
534
+ // Output tokens pie chart
535
+ this.outputChart = new Chart(outputCtx, {
536
+ type: 'pie',
537
+ data: {
538
+ labels: names,
539
+ datasets: [{
540
+ data: outputTokens,
541
+ backgroundColor: colors
542
+ }]
543
+ },
544
+ options: {
545
+ plugins: {
546
+ legend: {
547
+ display: true,
548
+ labels: {
549
+ color: textColor
550
+ }
551
+ },
552
+ datalabels: {
553
+ display: true,
554
+ color: 'white',
555
+ font: { weight: 'bold' },
556
+ formatter: (value) => value
557
+ }
558
+ }
559
+ }
560
+ });
561
+
562
+ // Combined input/output tokens bar chart
563
+ this.tokensChart = new Chart(tokensCtx, {
564
+ type: 'bar',
565
+ data: {
566
+ labels: names,
567
+ datasets: [
568
+ {
569
+ label: 'Input Tokens',
570
+ data: inputTokens,
571
+ backgroundColor: colors.map(color => color + '80'), // Semi-transparent
572
+ borderColor: colors,
573
+ borderWidth: 2,
574
+ borderSkipped: false,
575
+ yAxisID: 'y'
576
+ },
577
+ {
578
+ label: 'Output Tokens',
579
+ data: outputTokens,
580
+ backgroundColor: colors,
581
+ yAxisID: 'y1'
582
+ }
583
+ ]
584
+ },
585
+ options: {
586
+ responsive: true,
587
+ plugins: {
588
+ legend: {
589
+ labels: {
590
+ color: textColor
591
+ }
592
+ }
593
+ },
594
+ scales: {
595
+ x: {
596
+ ticks: {
597
+ color: textColor
598
+ },
599
+ grid: {
600
+ color: gridColor
601
+ }
602
+ },
603
+ y: {
604
+ type: 'linear',
605
+ display: true,
606
+ position: 'left',
607
+ beginAtZero: true,
608
+ title: {
609
+ display: true,
610
+ text: 'Input Tokens',
611
+ color: textColor
612
+ },
613
+ ticks: {
614
+ color: textColor
615
+ },
616
+ grid: {
617
+ color: gridColor
618
+ }
619
+ },
620
+ y1: {
621
+ type: 'linear',
622
+ display: true,
623
+ position: 'right',
624
+ beginAtZero: true,
625
+ title: {
626
+ display: true,
627
+ text: 'Output Tokens',
628
+ color: textColor
629
+ },
630
+ ticks: {
631
+ color: textColor
632
+ },
633
+ grid: {
634
+ drawOnChartArea: false,
635
+ color: gridColor
636
+ }
637
+ }
638
+ }
639
+ }
640
+ });
641
+ }
642
+
643
+ generateColors(count) {
644
+ const colors = [
645
+ '#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF',
646
+ '#FF9F40', '#FF6384', '#C9CBCF', '#4BC0C0', '#FF6384'
647
+ ];
648
+ return Array.from({length: count}, (_, i) => colors[i % colors.length]);
649
+ }
650
+
651
+ updateSummaryTable(totalCalls, totalInputTokens, totalOutputTokens) {
652
+ const tableHtml = `
653
+ <table class="stats-summary">
654
+ <tr><th>Metric</th><th>Total</th></tr>
655
+ <tr><td>Tool Calls</td><td>${totalCalls}</td></tr>
656
+ <tr><td>Input Tokens</td><td>${totalInputTokens}</td></tr>
657
+ <tr><td>Output Tokens</td><td>${totalOutputTokens}</td></tr>
658
+ <tr><td>Total Tokens</td><td>${totalInputTokens + totalOutputTokens}</td></tr>
659
+ </table>
660
+ `;
661
+ $('#stats-summary').html(tableHtml);
662
+ }
663
+
664
+ initializeTheme() {
665
+ // Check if user has manually set a theme preference
666
+ const savedTheme = localStorage.getItem('serena-theme');
667
+
668
+ if (savedTheme) {
669
+ // User has manually set a preference, use it
670
+ this.setTheme(savedTheme);
671
+ } else {
672
+ // No manual preference, detect system color scheme
673
+ this.detectSystemTheme();
674
+ }
675
+
676
+ // Listen for system theme changes
677
+ this.setupSystemThemeListener();
678
+ }
679
+
680
+ detectSystemTheme() {
681
+ // Check if system prefers dark mode
682
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
683
+ const theme = prefersDark ? 'dark' : 'light';
684
+ this.setTheme(theme);
685
+ }
686
+
687
+ setupSystemThemeListener() {
688
+ // Listen for changes in system color scheme
689
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
690
+
691
+ const handleSystemThemeChange = (e) => {
692
+ // Only auto-switch if user hasn't manually set a preference
693
+ const savedTheme = localStorage.getItem('serena-theme');
694
+ if (!savedTheme) {
695
+ const newTheme = e.matches ? 'dark' : 'light';
696
+ this.setTheme(newTheme);
697
+ }
698
+ };
699
+
700
+ // Add listener for system theme changes
701
+ if (mediaQuery.addEventListener) {
702
+ mediaQuery.addEventListener('change', handleSystemThemeChange);
703
+ } else {
704
+ // Fallback for older browsers
705
+ mediaQuery.addListener(handleSystemThemeChange);
706
+ }
707
+ }
708
+
709
+ toggleTheme() {
710
+ const currentTheme = document.documentElement.getAttribute('data-theme') || 'light';
711
+ const newTheme = currentTheme === 'light' ? 'dark' : 'light';
712
+
713
+ // When user manually toggles, save their preference
714
+ localStorage.setItem('serena-theme', newTheme);
715
+ this.setTheme(newTheme);
716
+ }
717
+
718
+ setTheme(theme) {
719
+ // Set the theme on the document element
720
+ document.documentElement.setAttribute('data-theme', theme);
721
+
722
+ // Update the toggle button
723
+ if (theme === 'dark') {
724
+ this.$themeIcon.text('☀️');
725
+ this.$themeText.text('Light');
726
+ } else {
727
+ this.$themeIcon.text('🌙');
728
+ this.$themeText.text('Dark');
729
+ }
730
+
731
+ // Update the logo based on theme
732
+ this.updateLogo(theme);
733
+
734
+ // Save to localStorage
735
+ localStorage.setItem('serena-theme', theme);
736
+
737
+ // Update charts if they exist
738
+ this.updateChartsTheme();
739
+ }
740
+
741
+ updateLogo(theme) {
742
+ const logoElement = document.getElementById('serena-logo');
743
+ if (logoElement) {
744
+ if (theme === 'dark') {
745
+ logoElement.src = 'serena-logs-dark-mode.png';
746
+ } else {
747
+ logoElement.src = 'serena-logs.png';
748
+ }
749
+ }
750
+ }
751
+
752
+ updateChartsTheme() {
753
+ const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
754
+ const textColor = isDark ? '#ffffff' : '#000000';
755
+ const gridColor = isDark ? '#444' : '#ddd';
756
+
757
+ // Update existing charts
758
+ if (this.countChart) {
759
+ this.countChart.options.scales.x.ticks.color = textColor;
760
+ this.countChart.options.scales.y.ticks.color = textColor;
761
+ this.countChart.options.scales.x.grid.color = gridColor;
762
+ this.countChart.options.scales.y.grid.color = gridColor;
763
+ this.countChart.update();
764
+ }
765
+
766
+ if (this.inputChart) {
767
+ this.inputChart.options.scales.x.ticks.color = textColor;
768
+ this.inputChart.options.scales.y.ticks.color = textColor;
769
+ this.inputChart.options.scales.x.grid.color = gridColor;
770
+ this.inputChart.options.scales.y.grid.color = gridColor;
771
+ this.inputChart.update();
772
+ }
773
+
774
+ if (this.outputChart) {
775
+ this.outputChart.options.scales.x.ticks.color = textColor;
776
+ this.outputChart.options.scales.y.ticks.color = textColor;
777
+ this.outputChart.options.scales.x.grid.color = gridColor;
778
+ this.outputChart.options.scales.y.grid.color = gridColor;
779
+ this.outputChart.update();
780
+ }
781
+
782
+ if (this.tokensChart) {
783
+ this.tokensChart.options.scales.x.ticks.color = textColor;
784
+ this.tokensChart.options.scales.y.ticks.color = textColor;
785
+ this.tokensChart.options.scales.y1.ticks.color = textColor;
786
+ this.tokensChart.options.scales.x.grid.color = gridColor;
787
+ this.tokensChart.options.scales.y.grid.color = gridColor;
788
+ this.tokensChart.options.scales.y1.grid.color = gridColor;
789
+ this.tokensChart.update();
790
+ }
791
+ }
792
+
793
+ shutdown() {
794
+ const self = this;
795
+ const _shutdown = function () {
796
+ console.log("Triggering shutdown");
797
+ $.ajax({
798
+ url: '/shutdown',
799
+ type: "PUT",
800
+ contentType: 'application/json',
801
+ });
802
+ self.$errorContainer.html('<div class="error-message">Shutting down ...</div>')
803
+ setTimeout(function() {
804
+ window.close();
805
+ }, 2000);
806
+ }
807
+
808
+ // ask for confirmation using a dialog
809
+ if (confirm("This will fully terminate the Serena server.")) {
810
+ _shutdown();
811
+ } else {
812
+ console.log("Shutdown cancelled");
813
+ }
814
+ }
815
+ }