vg-coder-cli 2.0.30 → 2.0.32

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 (40) hide show
  1. package/ARCHITECTURE.md +255 -0
  2. package/README.md +0 -11
  3. package/change.sh +0 -0
  4. package/dist/vg-coder-bundle.js +42 -0
  5. package/gulpfile.js +111 -0
  6. package/package.json +19 -11
  7. package/scripts/postinstall.js +13 -3
  8. package/src/index.js +28 -220
  9. package/src/server/api-server.js +120 -428
  10. package/src/server/views/css/bubble.css +81 -0
  11. package/src/server/views/css/code-viewer.css +58 -0
  12. package/src/server/views/css/terminal.css +59 -155
  13. package/src/server/views/dashboard.css +78 -678
  14. package/src/server/views/dashboard.html +39 -278
  15. package/src/server/views/js/api.js +2 -22
  16. package/src/server/views/js/config.js +27 -15
  17. package/src/server/views/js/event-protocol.js +263 -0
  18. package/src/server/views/js/features/bubble-features/index.js +125 -0
  19. package/src/server/views/js/features/bubble-features/paste-run-feature.js +16 -0
  20. package/src/server/views/js/features/bubble-features/terminal-feature.js +16 -0
  21. package/src/server/views/js/features/bubble.js +175 -0
  22. package/src/server/views/js/features/code-viewer.js +90 -0
  23. package/src/server/views/js/features/commands.js +34 -81
  24. package/src/server/views/js/features/editor-tabs.js +19 -46
  25. package/src/server/views/js/features/git-view.js +63 -81
  26. package/src/server/views/js/features/iframe-manager.js +3 -97
  27. package/src/server/views/js/features/monaco-manager.js +19 -39
  28. package/src/server/views/js/features/project-switcher.js +7 -63
  29. package/src/server/views/js/features/resize.js +5 -16
  30. package/src/server/views/js/features/structure.js +38 -106
  31. package/src/server/views/js/features/terminal.js +102 -418
  32. package/src/server/views/js/handlers.js +60 -43
  33. package/src/server/views/js/main.js +75 -179
  34. package/src/server/views/js/shadow-entry.js +21 -0
  35. package/src/server/views/js/utils.js +48 -28
  36. package/src/server/views/vg-coder/_metadata/generated_indexed_rulesets/_ruleset1 +0 -0
  37. package/src/server/views/vg-coder/controller.js +33 -258
  38. package/vetgo-auto/chrome/src/utils/injector-script.ts +33 -258
  39. package/vetgo-auto/vg-coder.zip +0 -0
  40. package/src/server/views/dashboard.js +0 -457
@@ -1,21 +1,49 @@
1
- // Event Handlers & Business Logic
2
- import { SYSTEM_PROMPT } from './config.js';
1
+ import { SYSTEM_PROMPT, API_BASE } from './config.js';
3
2
  import { analyzeProject, executeScript, copyToClipboard, readFromClipboard } from './api.js';
4
- import { showToast, showLoading, resetButton, showResponse, showCopiedState } from './utils.js';
5
- // Import dedicated feature handlers
6
- import { handleStructureView, handleToggleFolder, handleCheckboxChange, handleCopySelected } from './features/structure.js';
3
+ import { showToast, showLoading, resetButton, showResponse, showCopiedState, getById } from './utils.js';
4
+ import { globalDispatcher, EVENT_TYPES } from './event-protocol.js';
7
5
 
8
6
  let lastAnalyzeResult = null;
9
7
 
10
- // ==========================================
11
- // SYSTEM PROMPT HANDLERS
12
- // ==========================================
8
+ /**
9
+ * Initialize event handlers for bubble menu features
10
+ * This creates the bridge between UI events and handler functions
11
+ */
12
+ export function initEventHandlers() {
13
+ // Paste & Run from Clipboard
14
+ globalDispatcher.on(EVENT_TYPES.PASTE_RUN, async (event) => {
15
+ console.log('[Handlers] Paste & Run event received:', event);
16
+ await executeFromClipboard();
17
+ });
18
+
19
+ // New Terminal
20
+ globalDispatcher.on(EVENT_TYPES.TERMINAL_NEW, (event) => {
21
+ console.log('[Handlers] New Terminal event received:', event);
22
+ if (typeof window.createNewTerminal === 'function') {
23
+ window.createNewTerminal();
24
+ } else {
25
+ showToast('Terminal feature not available', 'error');
26
+ }
27
+ });
28
+
29
+ // Terminal Execute (for future use)
30
+ globalDispatcher.on(EVENT_TYPES.TERMINAL_EXECUTE, (event) => {
31
+ console.log('[Handlers] Terminal Execute event received:', event);
32
+ const { command, terminalId } = event.payload || {};
33
+ if (command) {
34
+ // TODO: Implement terminal execute logic
35
+ console.log(`Execute in terminal ${terminalId}: ${command}`);
36
+ }
37
+ });
38
+
39
+ console.log('[Handlers] Event handlers initialized');
40
+ }
13
41
 
14
42
  export function toggleSystemPrompt() {
15
- const content = document.getElementById('system-prompt-content');
16
- const icon = document.getElementById('toggle-icon');
17
- content.classList.toggle('open');
18
- icon.classList.toggle('open');
43
+ const content = getById('system-prompt-content');
44
+ const icon = getById('toggle-icon');
45
+ if(content) content.classList.toggle('open');
46
+ if(icon) icon.classList.toggle('open');
19
47
  }
20
48
 
21
49
  export function copySystemPromptFromHeader(event) {
@@ -33,8 +61,8 @@ export function copySystemPromptFromHeader(event) {
33
61
 
34
62
  export function copySystemPrompt(event) {
35
63
  const copyBtn = event.target.closest('.btn-copy');
36
- const copyIcon = document.getElementById('copy-icon');
37
- const copyText = document.getElementById('copy-text');
64
+ const copyIcon = getById('copy-icon');
65
+ const copyText = getById('copy-text');
38
66
 
39
67
  navigator.clipboard.writeText(SYSTEM_PROMPT).then(() => {
40
68
  showCopiedState(copyBtn, copyIcon, copyText, '📋', 'Copy System Prompt');
@@ -42,13 +70,12 @@ export function copySystemPrompt(event) {
42
70
  }).catch(err => showToast('Lỗi copy: ' + err.message, 'error'));
43
71
  }
44
72
 
45
- // ==========================================
46
- // ANALYZE HANDLERS
47
- // ==========================================
48
-
49
73
  export async function testAnalyze(event) {
50
74
  const btn = event.target.closest('.btn') || event.target.closest('.btn-icon-head');
51
- const path = document.getElementById('analyze-path').value;
75
+ const pathInput = getById('analyze-path');
76
+ if (!pathInput) return;
77
+
78
+ const path = pathInput.value;
52
79
 
53
80
  showLoading(btn, btn.innerHTML);
54
81
  try {
@@ -78,11 +105,12 @@ export async function testAnalyze(event) {
78
105
 
79
106
  export async function copyAnalyzeResult(event) {
80
107
  const copyBtn = event.target.closest('.btn-copy');
81
- const copyIcon = document.getElementById('analyze-copy-icon');
82
- const copyText = document.getElementById('analyze-copy-text');
108
+ const copyIcon = getById('analyze-copy-icon');
109
+ const copyText = getById('analyze-copy-text');
110
+ const pathInput = getById('analyze-path');
83
111
 
84
- if (!lastAnalyzeResult) {
85
- const path = document.getElementById('analyze-path').value;
112
+ if (!lastAnalyzeResult && pathInput) {
113
+ const path = pathInput.value;
86
114
  showLoading(copyBtn, copyBtn.innerHTML);
87
115
  try {
88
116
  lastAnalyzeResult = await analyzeProject(path);
@@ -103,13 +131,11 @@ export async function copyAnalyzeResult(event) {
103
131
  }
104
132
  }
105
133
 
106
- // ==========================================
107
- // EXECUTE HANDLERS
108
- // ==========================================
109
-
110
134
  export async function testExecute(event) {
111
135
  const btn = event.target.closest('.btn');
112
- const bashInput = document.getElementById('execute-bash');
136
+ const bashInput = getById('execute-bash');
137
+ if (!bashInput) return;
138
+
113
139
  const bash = bashInput.value;
114
140
 
115
141
  if (!bash.trim()) {
@@ -131,15 +157,16 @@ export async function testExecute(event) {
131
157
  }
132
158
 
133
159
  export async function executeFromClipboard(event) {
134
- const btn = event.target.closest('.btn');
135
- const bashInput = document.getElementById('execute-bash');
160
+ const btn = event?.target?.closest('.btn');
161
+ const bashInput = getById('execute-bash');
162
+ if (!bashInput) return;
136
163
 
137
- showLoading(btn, btn.innerHTML);
164
+ if (btn) showLoading(btn, btn.innerHTML);
138
165
  try {
139
166
  const clipboardText = await readFromClipboard();
140
167
  if (!clipboardText || !clipboardText.trim()) {
141
168
  showToast('Clipboard trống!', 'error');
142
- resetButton(btn);
169
+ if (btn) resetButton(btn);
143
170
  return;
144
171
  }
145
172
  bashInput.value = clipboardText;
@@ -160,13 +187,9 @@ export async function executeFromClipboard(event) {
160
187
  showToast('Lỗi: ' + err.message, 'error');
161
188
  }
162
189
  }
163
- resetButton(btn);
190
+ if (btn) resetButton(btn);
164
191
  }
165
192
 
166
- // ==========================================
167
- // EXPORT TO WINDOW (GLOBAL)
168
- // ==========================================
169
-
170
193
  window.toggleSystemPrompt = toggleSystemPrompt;
171
194
  window.copySystemPrompt = copySystemPrompt;
172
195
  window.copySystemPromptFromHeader = copySystemPromptFromHeader;
@@ -174,9 +197,3 @@ window.testAnalyze = testAnalyze;
174
197
  window.copyAnalyzeResult = copyAnalyzeResult;
175
198
  window.testExecute = testExecute;
176
199
  window.executeFromClipboard = executeFromClipboard;
177
-
178
- // Map Structure handlers from feature module to window
179
- window.testStructure = handleStructureView;
180
- window.toggleFolder = handleToggleFolder;
181
- window.handleCheckboxChange = handleCheckboxChange;
182
- window.copySelectedStructure = handleCopySelected;
@@ -1,100 +1,66 @@
1
- // Main entry point - Initialize application
2
- import { SYSTEM_PROMPT } from './config.js';
1
+ import { API_BASE, SYSTEM_PROMPT } from './config.js';
3
2
  import { checkHealth } from './api.js';
4
- import './handlers.js';
5
- import { showToast, showCopiedState } from './utils.js';
6
- import { initIframeManager } from './features/iframe-manager.js';
3
+ import { initEventHandlers } from './handlers.js';
4
+ import { showToast, getById, setRoot, qs } from './utils.js';
7
5
  import { initGitView } from './features/git-view.js';
8
- import { initTerminal, createNewTerminal } from './features/terminal.js';
6
+ import { initTerminal } from './features/terminal.js';
9
7
  import { initEditorTabs } from './features/editor-tabs.js';
10
- import { initMonaco, updateMonacoTheme } from './features/monaco-manager.js';
8
+ // REMOVED: initMonaco
11
9
  import { initResizeHandler } from './features/resize.js';
12
10
  import { initSavedCommands } from './features/commands.js';
13
11
  import { initProjectSwitcher } from './features/project-switcher.js';
12
+ import './features/structure.js';
13
+ import { initBubble } from './features/bubble.js';
14
14
 
15
- document.addEventListener('DOMContentLoaded', async () => {
16
- // Load system prompt text
17
- document.getElementById('prompt-text').textContent = SYSTEM_PROMPT;
18
-
19
- // Check server status
20
- await checkServerStatus();
15
+ export async function initMain() {
16
+ console.log('VG Coder: Starting Main Logic...');
21
17
 
22
- // Load Project Info
23
- await loadProjectInfo();
24
-
25
- // Initialize Theme
26
- initTheme();
27
-
28
- // Load Extension Path
29
- loadExtensionPath();
30
-
31
- // Initialize Iframe Manager
32
- initIframeManager();
33
-
34
- // Initialize Git View
35
- initGitView();
36
-
37
- // Initialize Terminal System
38
- initTerminal();
18
+ try {
19
+ const promptEl = getById('prompt-text');
20
+ if (promptEl) promptEl.textContent = SYSTEM_PROMPT;
21
+
22
+ await checkServerStatus();
23
+ await loadProjectInfo();
39
24
 
40
- // Initialize Monaco & Tabs
41
- // Initialize Monaco & Tabs
42
- initMonaco();
43
- initEditorTabs();
25
+ initTheme();
26
+ loadExtensionPath();
27
+
28
+ // Initialize event handlers FIRST (before bubble which dispatches events)
29
+ initEventHandlers();
30
+
31
+ initGitView();
32
+ initTerminal();
33
+ initEditorTabs();
34
+ initResizeHandler();
35
+ initSavedCommands();
36
+ await initProjectSwitcher();
37
+
38
+ // Init Bubble (will use event protocol)
39
+ initBubble();
44
40
 
45
- // Initialize Resize Handler
46
- initResizeHandler();
47
-
48
- // Initialize Saved Commands
49
- initSavedCommands();
50
-
51
- // Initialize Project Switcher
52
- await initProjectSwitcher();
53
-
54
- // Set default tab to AI Assistant
55
- if (window.switchTab) {
56
- window.switchTab('ai-assistant');
41
+ console.log('✅ VG Coder: Initialization Complete');
42
+ } catch (e) {
43
+ console.error('VG Coder Init Failed:', e);
57
44
  }
58
- });
45
+ }
59
46
 
60
- // Global event handler for project switches
47
+ // ... (Giữ nguyên các hàm helper khác: switchProject, loadProjectInfo, initTheme...)
48
+ // Để ngắn gọn, tôi copy lại phần còn lại của main.js
61
49
  window.addEventListener('project-switched', async (event) => {
62
- const { projectId, projectName, project } = event.detail;
63
- console.log(`Project switched to: ${projectName}`);
64
-
65
- // Reload project info
50
+ const { projectId, projectName } = event.detail;
66
51
  await loadProjectInfo();
67
-
68
- // Filter terminal visibility (show only terminals for active project)
69
- if (window.updateTerminalVisibility) {
70
- window.updateTerminalVisibility(projectId);
71
- }
72
-
73
- // Reload saved commands for new project
74
- if (window.loadSavedCommands) {
75
- await window.loadSavedCommands();
76
- }
77
-
78
- // Reset tree view (hide it)
79
- const treeContainer = document.getElementById('structure-tree');
80
- if (treeContainer) {
81
- treeContainer.style.display = 'none';
82
- }
83
-
84
- // Clear tree content
85
- const treeContent = document.getElementById('tree-content');
86
- if (treeContent) {
87
- treeContent.innerHTML = '';
88
- }
89
-
90
- // TODO: Refresh other context-dependent components
91
- // - Git view refresh
52
+ if (window.updateTerminalVisibility) window.updateTerminalVisibility(projectId);
53
+ if (window.loadSavedCommands) await window.loadSavedCommands();
54
+ const treeContainer = getById('structure-tree');
55
+ if (treeContainer) treeContainer.style.display = 'none';
56
+ const treeContent = getById('tree-content');
57
+ if (treeContent) treeContent.innerHTML = '';
92
58
  });
93
59
 
94
60
  async function checkServerStatus() {
95
- const statusEl = document.getElementById('status');
61
+ const statusEl = getById('status');
62
+ if (!statusEl) return;
96
63
  const isHealthy = await checkHealth();
97
-
98
64
  if (isHealthy) {
99
65
  statusEl.textContent = '●';
100
66
  statusEl.style.background = 'transparent';
@@ -108,129 +74,59 @@ async function checkServerStatus() {
108
74
 
109
75
  async function loadProjectInfo() {
110
76
  try {
111
- // Fetch info for current directory (.)
112
- const res = await fetch('/api/info?path=.');
77
+ const res = await fetch(`${API_BASE}/api/info?path=.`);
113
78
  const data = await res.json();
114
-
115
- const projectNameEl = document.getElementById('project-name');
116
- const projectMetaEl = document.getElementById('project-meta');
117
-
118
- // Extract folder name from path
119
- const fullPath = data.path;
120
- // Handle both Windows (\) and Unix (/) paths
121
- const folderName = fullPath.split(/[\\/]/).pop();
122
-
123
- projectNameEl.textContent = folderName;
124
- projectMetaEl.textContent = `${data.primaryType} • ${fullPath}`;
125
-
126
- } catch (err) {
127
- console.error('Failed to load project info:', err);
128
- document.getElementById('project-name').textContent = 'Unknown Project';
129
- document.getElementById('project-meta').textContent = 'Error loading info';
130
- }
79
+ const projectNameEl = getById('project-name');
80
+ const projectMetaEl = getById('project-meta');
81
+ if (projectNameEl) projectNameEl.textContent = data.path.split(/[\\/]/).pop();
82
+ if (projectMetaEl) projectMetaEl.textContent = `${data.primaryType} • ${data.path}`;
83
+ } catch (err) {}
131
84
  }
132
85
 
133
86
  function initTheme() {
134
- const themeBtn = document.getElementById('theme-toggle');
87
+ const themeBtn = getById('theme-toggle');
88
+ if (!themeBtn) return;
135
89
  let currentTheme = localStorage.getItem('theme') || 'light';
136
- updateThemeIcon(currentTheme);
137
-
90
+ applyTheme(currentTheme);
138
91
  themeBtn.addEventListener('click', () => {
139
92
  const newTheme = currentTheme === 'light' ? 'dark' : 'light';
140
- document.documentElement.setAttribute('data-theme', newTheme);
141
93
  localStorage.setItem('theme', newTheme);
142
94
  currentTheme = newTheme;
143
- updateThemeIcon(newTheme);
144
- updateMonacoTheme(newTheme);
95
+ applyTheme(newTheme);
145
96
  });
146
97
  }
147
98
 
148
- function updateThemeIcon(theme) {
149
- const themeIcon = document.getElementById('theme-icon');
150
- if (theme === 'dark') {
151
- themeIcon.textContent = '☀️';
152
- } else {
153
- themeIcon.textContent = '🌙';
154
- }
99
+ function applyTheme(theme) {
100
+ const themeIcon = getById('theme-icon');
101
+ if (themeIcon) themeIcon.textContent = theme === 'dark' ? '☀️' : '🌙';
102
+ const wrapper = getById('vg-app-root');
103
+ if (wrapper) wrapper.setAttribute('data-theme', theme);
155
104
  }
156
105
 
157
106
  async function loadExtensionPath() {
158
107
  try {
159
- const res = await fetch('/api/extension-path');
108
+ const res = await fetch(`${API_BASE}/api/extension-path`);
160
109
  const data = await res.json();
161
- // Updated ID for the input in center guide
162
- const input = document.getElementById('extension-path-input-center');
163
- if (input) {
164
- if (data.exists) input.value = data.path;
165
- else {
166
- input.value = "Error: Extension folder not found.";
167
- input.style.color = "var(--ios-red)";
168
- }
169
- }
110
+ const input = getById('extension-path-input-center');
111
+ if (input && data.exists) input.value = data.path;
170
112
  } catch (err) {}
171
113
  }
172
114
 
173
- window.copyExtensionPath = function(event) {
174
- const input = document.getElementById('extension-path-input-center');
175
- const btn = event.currentTarget;
176
- const originalText = btn.textContent;
177
-
178
- navigator.clipboard.writeText(input.value).then(() => {
179
- btn.textContent = '✓';
180
- btn.style.background = 'var(--ios-green)';
181
- btn.style.color = 'white';
182
- btn.style.borderColor = 'var(--ios-green)';
183
- showToast('Đã copy đường dẫn extension', 'success');
184
-
185
- setTimeout(() => {
186
- btn.textContent = originalText;
187
- btn.style.background = '';
188
- btn.style.color = '';
189
- btn.style.borderColor = '';
190
- }, 2000);
191
- });
192
- }
193
-
194
- window.copyChromeUrl = function(event) {
195
- const input = document.getElementById('chrome-url-input-center');
196
- const btn = event.currentTarget;
197
- const originalText = btn.textContent;
198
-
199
- navigator.clipboard.writeText(input.value).then(() => {
200
- btn.textContent = '✓';
201
- btn.style.background = 'var(--ios-green)';
202
- btn.style.color = 'white';
203
- btn.style.borderColor = 'var(--ios-green)';
204
- showToast('Đã copy URL', 'success');
205
-
206
- setTimeout(() => {
207
- btn.textContent = originalText;
208
- btn.style.background = '';
209
- btn.style.color = '';
210
- btn.style.borderColor = '';
211
- }, 2000);
212
- });
213
- }
214
-
215
115
  window.stopServer = async function() {
216
- if (!confirm('Are you sure you want to stop the server?')) {
217
- return;
218
- }
219
-
116
+ if (!confirm('Are you sure you want to stop the server?')) return;
220
117
  try {
221
- await fetch('/api/shutdown', { method: 'POST' });
118
+ await fetch(`${API_BASE}/api/shutdown`, { method: 'POST' });
222
119
  showToast('Server stopped successfully', 'success');
223
-
224
- // Show a message that server is stopped
225
120
  setTimeout(() => {
226
- document.body.innerHTML = `
227
- <div style="display: flex; align-items: center; justify-content: center; height: 100vh; flex-direction: column; gap: 20px;">
228
- <h2>🛑 Server Stopped</h2>
229
- <p>You can close this tab now.</p>
230
- </div>
231
- `;
121
+ const wrapper = getById('vg-app-root');
122
+ if(wrapper) wrapper.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100vh;"><h2>🛑 Server Stopped</h2></div>';
232
123
  }, 1000);
233
- } catch (error) {
234
- console.error('Failed to stop server:', error);
235
- }
124
+ } catch (error) {}
125
+ }
126
+
127
+ if (!window.__VG_CODER_ROOT__) {
128
+ document.addEventListener('DOMContentLoaded', () => {
129
+ setRoot(document);
130
+ initMain();
131
+ });
236
132
  }
@@ -0,0 +1,21 @@
1
+ import { initMain } from './main.js';
2
+ import { setRoot } from './utils.js';
3
+
4
+ // Entry point for Shadow DOM Injector
5
+ (function() {
6
+ console.log('🚀 VG Coder: Initializing in Shadow Context...');
7
+
8
+ // 1. Determine Root (ShadowRoot exposed by gulp wrapper)
9
+ const root = window.__VG_CODER_ROOT__;
10
+
11
+ if (!root) {
12
+ console.error('VG Coder: Root context not found!');
13
+ return;
14
+ }
15
+
16
+ // 2. Set Context for Utils
17
+ setRoot(root);
18
+
19
+ // 3. Initialize Main App Logic
20
+ initMain();
21
+ })();
@@ -1,29 +1,57 @@
1
- // UI Utility Functions
1
+ // UI Utility Functions & DOM Helpers
2
+
3
+ // --- Context Management ---
4
+ let _root = document;
5
+
6
+ export function setRoot(rootElement) {
7
+ _root = rootElement;
8
+ // Update global reference if in Shadow DOM
9
+ if (rootElement.host) {
10
+ window.__VG_CODER_ROOT__ = rootElement;
11
+ }
12
+ }
13
+
14
+ export function getRoot() {
15
+ return _root;
16
+ }
17
+
18
+ // DOM Helpers to replace document.*
19
+ export function qs(selector) {
20
+ return _root.querySelector(selector);
21
+ }
22
+
23
+ export function qsa(selector) {
24
+ return _root.querySelectorAll(selector);
25
+ }
26
+
27
+ export function getById(id) {
28
+ // ShadowRoot doesn't always support getElementById in standard way, querySelector is safer
29
+ return _root.querySelector('#' + id);
30
+ }
31
+
32
+ // --- UI Utils ---
2
33
 
3
34
  /**
4
35
  * Display a toast notification
5
- * @param {string} message - Message to display
6
- * @param {string} type - Type of toast: 'success', 'error', 'info'
7
36
  */
8
37
  export function showToast(message, type = 'success') {
9
- const toast = document.getElementById('toast');
10
- // Reset text content to remove potential icon junk
38
+ const toast = getById('toast');
39
+ if (!toast) return;
40
+
41
+ // Reset text content
11
42
  toast.textContent = message;
12
43
  toast.className = `toast ${type}`;
13
44
  toast.classList.add('show');
14
45
 
15
- // Clear previous timeout if exists
16
46
  if (toast.timeoutId) clearTimeout(toast.timeoutId);
17
-
18
47
  toast.timeoutId = setTimeout(() => toast.classList.remove('show'), 3000);
19
48
  }
20
49
 
21
50
  /**
22
51
  * Show loading state on button
23
- * @param {HTMLElement} button - Button element
24
- * @param {string} originalText - Original button HTML
25
52
  */
26
53
  export function showLoading(button, originalText) {
54
+ if (!button) return;
27
55
  button.disabled = true;
28
56
  button.innerHTML = '<span class="loading"></span>';
29
57
  button.dataset.originalText = originalText;
@@ -31,50 +59,42 @@ export function showLoading(button, originalText) {
31
59
 
32
60
  /**
33
61
  * Reset button to original state
34
- * @param {HTMLElement} button - Button element
35
62
  */
36
63
  export function resetButton(button) {
64
+ if (!button) return;
37
65
  button.disabled = false;
38
66
  const originalText = button.dataset.originalText;
39
- button.innerHTML = originalText;
67
+ if (originalText) button.innerHTML = originalText;
40
68
  }
41
69
 
42
70
  /**
43
- * Display API response in response area
44
- * @param {string} elementId - ID of response element
45
- * @param {Object} data - Response data
46
- * @param {boolean} isError - Whether this is an error response
71
+ * Display API response
47
72
  */
48
73
  export function showResponse(elementId, data, isError = false) {
49
- const el = document.getElementById(elementId);
74
+ const el = getById(elementId);
75
+ if (!el) return;
50
76
  el.className = 'response show ' + (isError ? 'error' : 'success');
51
77
  el.innerHTML = '<pre>' + JSON.stringify(data, null, 2) + '</pre>';
52
78
  }
53
79
 
54
80
  /**
55
81
  * Update button state to show copied status
56
- * @param {HTMLElement} button - Button element
57
- * @param {HTMLElement} icon - Icon element
58
- * @param {HTMLElement} text - Text element
59
- * @param {string} originalIcon - Original icon text
60
- * @param {string} originalText - Original button text
61
82
  */
62
83
  export function showCopiedState(button, icon, text, originalIcon, originalText) {
84
+ if (!button) return;
63
85
  button.classList.add('copied');
64
- icon.textContent = '✓';
65
- text.textContent = 'Copied';
86
+ if (icon) icon.textContent = '✓';
87
+ if (text) text.textContent = 'Copied';
66
88
 
67
89
  setTimeout(() => {
68
90
  button.classList.remove('copied');
69
- icon.textContent = originalIcon;
70
- text.textContent = originalText;
91
+ if (icon) icon.textContent = originalIcon;
92
+ if (text) text.textContent = originalText;
71
93
  }, 2000);
72
94
  }
73
95
 
74
96
  /**
75
- * Format number with commas (handles undefined/null)
76
- * @param {number} num - Number to format
77
- * @returns {string} Formatted string
97
+ * Format number with commas
78
98
  */
79
99
  export function formatNumber(num) {
80
100
  if (num === undefined || num === null) return '0';