agentgui 1.0.30 → 1.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.
package/database.js CHANGED
@@ -245,16 +245,34 @@ export const queries = {
245
245
  },
246
246
 
247
247
  getMessage(id) {
248
- const stmt = db.prepare('SELECT * FROM messages WHERE id = ?');
249
- return stmt.get(id);
250
- },
251
-
252
- getConversationMessages(conversationId) {
253
- const stmt = db.prepare(
254
- 'SELECT * FROM messages WHERE conversationId = ? ORDER BY created_at ASC'
255
- );
256
- return stmt.all(conversationId);
257
- },
248
+ const stmt = db.prepare('SELECT * FROM messages WHERE id = ?');
249
+ const msg = stmt.get(id);
250
+ if (msg && typeof msg.content === 'string') {
251
+ try {
252
+ msg.content = JSON.parse(msg.content);
253
+ } catch (_) {
254
+ // If it's not JSON, leave it as string
255
+ }
256
+ }
257
+ return msg;
258
+ },
259
+
260
+ getConversationMessages(conversationId) {
261
+ const stmt = db.prepare(
262
+ 'SELECT * FROM messages WHERE conversationId = ? ORDER BY created_at ASC'
263
+ );
264
+ const messages = stmt.all(conversationId);
265
+ return messages.map(msg => {
266
+ if (typeof msg.content === 'string') {
267
+ try {
268
+ msg.content = JSON.parse(msg.content);
269
+ } catch (_) {
270
+ // If it's not JSON, leave it as string
271
+ }
272
+ }
273
+ return msg;
274
+ });
275
+ },
258
276
 
259
277
  createSession(conversationId) {
260
278
  const id = generateId('sess');
@@ -499,19 +517,25 @@ export const queries = {
499
517
  if (content && !content.startsWith('[{"tool_use_id"')) {
500
518
  messages.push({ id: obj.uuid || generateId('msg'), role: 'user', content, created_at: new Date(obj.timestamp).getTime() });
501
519
  }
502
- } else if (obj.type === 'assistant' && obj.message?.content) {
503
- let text = '';
504
- const content = obj.message.content;
505
- if (Array.isArray(content)) {
506
- for (const c of content) {
507
- if (c.type === 'text' && c.text) text += c.text;
508
- }
509
- } else if (typeof content === 'string') {
510
- text = content;
511
- }
512
- if (text) {
513
- messages.push({ id: obj.uuid || generateId('msg'), role: 'assistant', content: text, created_at: new Date(obj.timestamp).getTime() });
514
- }
520
+ } else if (obj.type === 'assistant' && obj.message?.content) {
521
+ let text = '';
522
+ const content = obj.message.content;
523
+ if (Array.isArray(content)) {
524
+ // CRITICAL FIX: Join text blocks with newlines to preserve separation
525
+ const textBlocks = [];
526
+ for (const c of content) {
527
+ if (c.type === 'text' && c.text) {
528
+ textBlocks.push(c.text);
529
+ }
530
+ }
531
+ // Join with double newline to preserve logical separation
532
+ text = textBlocks.join('\n\n');
533
+ } else if (typeof content === 'string') {
534
+ text = content;
535
+ }
536
+ if (text) {
537
+ messages.push({ id: obj.uuid || generateId('msg'), role: 'assistant', content: text, created_at: new Date(obj.timestamp).getTime() });
538
+ }
515
539
  }
516
540
  } catch (_) {}
517
541
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.30",
3
+ "version": "1.0.32",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/static/app.js CHANGED
@@ -96,10 +96,15 @@ class GMGUIApp {
96
96
  console.error('[CRITICAL] GMGUIApp.init() failed:', err);
97
97
  console.error('[CRITICAL] Stack:', err.stack);
98
98
  throw err;
99
- });
100
- }
99
+ });
100
+ }
101
+
102
+ // Helper for authenticated API calls - ensures credentials sent for proxy auth
103
+ async apiFetch(url, options = {}) {
104
+ return fetch(url, { credentials: 'include', ...options });
105
+ }
101
106
 
102
- async init() {
107
+ async init() {
103
108
  console.log('[DEBUG] Init: Starting initialization');
104
109
  console.log('[DEBUG] Init: BASE_URL =', BASE_URL);
105
110
  console.log('[DEBUG] Init: Window width:', window.innerWidth);
@@ -155,7 +160,7 @@ class GMGUIApp {
155
160
  async verifyConsistency() {
156
161
  // Silent consistency check - only log if mismatch found
157
162
  try {
158
- const res = await fetch(BASE_URL + '/api/conversations');
163
+ const res = await this.apiFetch(BASE_URL + '/api/conversations');
159
164
  if (!res.ok) return;
160
165
 
161
166
  const data = await res.json();
@@ -176,7 +181,7 @@ class GMGUIApp {
176
181
 
177
182
  async autoImportClaudeCode() {
178
183
  try {
179
- await fetch(BASE_URL + '/api/import/claude-code');
184
+ await this.apiFetch(BASE_URL + '/api/import/claude-code');
180
185
  } catch (e) {
181
186
  console.error('autoImportClaudeCode:', e);
182
187
  }
@@ -356,7 +361,7 @@ class GMGUIApp {
356
361
 
357
362
  async fetchHome() {
358
363
  try {
359
- const res = await fetch(BASE_URL + '/api/home');
364
+ const res = await this.apiFetch(BASE_URL + '/api/home');
360
365
  if (res.ok) {
361
366
  const data = await res.json();
362
367
  localStorage.setItem('gmgui-home', data.home);
@@ -391,35 +396,129 @@ class GMGUIApp {
391
396
  return p.startsWith('~') ? p.replace('~', home) : p;
392
397
  }
393
398
 
394
- setupEventListeners() {
395
- window.addEventListener('focus', () => {
396
- this.autoImportClaudeCode().then(() => {
397
- this.fetchConversations().then(() => this.renderChatHistory());
398
- });
399
- });
400
- const input = document.getElementById('messageInput');
401
- if (input) {
402
- input.addEventListener('keydown', (e) => {
403
- if (e.key === 'Enter' && !e.shiftKey) {
404
- e.preventDefault();
405
- this.sendMessage();
399
+ setupEventListeners() {
400
+ window.addEventListener('focus', () => {
401
+ this.autoImportClaudeCode().then(() => {
402
+ this.fetchConversations().then(() => this.renderChatHistory());
403
+ });
404
+ });
405
+
406
+ // THEME CHANGE LISTENER: Update HTML blocks when theme changes
407
+ // Listen for theme changes on document element
408
+ const themeObserver = new MutationObserver(() => {
409
+ console.log('[THEME] Theme changed, updating HTML blocks');
410
+ this.updateHtmlBlockThemes();
411
+ });
412
+
413
+ themeObserver.observe(document.documentElement, {
414
+ attributes: true,
415
+ attributeFilter: ['data-theme']
416
+ });
417
+
418
+ // Also listen for system theme changes
419
+ if (window.matchMedia) {
420
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
421
+ console.log('[THEME] System theme changed, updating HTML blocks');
422
+ this.updateHtmlBlockThemes();
423
+ });
424
+ }
425
+
426
+ const input = document.getElementById('messageInput');
427
+ if (input) {
428
+ input.addEventListener('keydown', (e) => {
429
+ if (e.key === 'Enter' && !e.shiftKey) {
430
+ e.preventDefault();
431
+ this.sendMessage();
432
+ }
433
+ });
434
+ input.addEventListener('input', () => this.updateSendButtonState());
435
+ }
436
+ document.getElementById('autoScroll')?.addEventListener('change', (e) => {
437
+ this.settings.autoScroll = e.target.checked;
438
+ this.saveSettings();
439
+ });
440
+ document.getElementById('connectTimeout')?.addEventListener('change', (e) => {
441
+ this.settings.connectTimeout = parseInt(e.target.value) * 1000;
442
+ this.saveSettings();
443
+ });
444
+ }
445
+
446
+ updateHtmlBlockThemes() {
447
+ // Update theme attribute and CSS for all existing HTML blocks
448
+ const currentTheme = document.documentElement.getAttribute('data-theme') ||
449
+ (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
450
+
451
+ // CRITICAL: Remove old theme styles and inject new ones for all HTML blocks
452
+ document.querySelectorAll('.html-content').forEach(content => {
453
+ // Remove any existing theme style tags
454
+ const oldStyles = content.querySelectorAll('style');
455
+ oldStyles.forEach(style => {
456
+ if (style.textContent.includes('.html-content')) {
457
+ style.remove();
458
+ }
459
+ });
460
+
461
+ // Inject new theme-aware CSS
462
+ const themeCSS = currentTheme === 'dark'
463
+ ? `<style>
464
+ .html-content {
465
+ color: #f8fafc;
466
+ background: transparent;
467
+ }
468
+ .html-content p { color: #cbd5e1; }
469
+ .html-content h1, .html-content h2, .html-content h3,
470
+ .html-content h4, .html-content h5, .html-content h6 {
471
+ color: #f8fafc;
472
+ }
473
+ .html-content a { color: #6366f1; }
474
+ .html-content code { color: #c7d2fe; background: rgba(0,0,0,0.3); }
475
+ .html-content pre { background: rgba(0,0,0,0.5); color: #e0e7ff; }
476
+ .html-content table { border-color: #334155; }
477
+ .html-content th { background: #1a202c; color: #f8fafc; }
478
+ .html-content td { border-color: #334155; }
479
+ .html-content blockquote { border-color: #334155; color: #cbd5e1; }
480
+ .html-content ul, .html-content ol { color: #cbd5e1; }
481
+ .html-content li { color: #cbd5e1; }
482
+ </style>`
483
+ : `<style>
484
+ .html-content {
485
+ color: #1d2129;
486
+ background: transparent;
487
+ }
488
+ .html-content p { color: #475569; }
489
+ .html-content h1, .html-content h2, .html-content h3,
490
+ .html-content h4, .html-content h5, .html-content h6 {
491
+ color: #1d2129;
492
+ }
493
+ .html-content a { color: #4f46e5; }
494
+ .html-content code { color: #6366f1; background: rgba(99,102,241,0.1); }
495
+ .html-content pre { background: #f3f4f6; color: #1d2129; }
496
+ .html-content table { border-color: #e5e7eb; }
497
+ .html-content th { background: #f9fafb; color: #1d2129; }
498
+ .html-content td { border-color: #e5e7eb; }
499
+ .html-content blockquote { border-color: #e5e7eb; color: #475569; }
500
+ .html-content ul, .html-content ol { color: #475569; }
501
+ .html-content li { color: #475569; }
502
+ </style>`;
503
+
504
+ // Create a temporary wrapper to parse and insert the style
505
+ const tempDiv = document.createElement('div');
506
+ tempDiv.innerHTML = themeCSS;
507
+ const styleEl = tempDiv.querySelector('style');
508
+ if (styleEl) {
509
+ content.insertBefore(styleEl.cloneNode(true), content.firstChild);
406
510
  }
511
+
512
+ // Update data-theme attribute
513
+ content.setAttribute('data-theme', currentTheme);
407
514
  });
408
- input.addEventListener('input', () => this.updateSendButtonState());
515
+
516
+ console.log(`[THEME] Updated ${document.querySelectorAll('.html-content').length} HTML blocks to ${currentTheme} mode`);
409
517
  }
410
- document.getElementById('autoScroll')?.addEventListener('change', (e) => {
411
- this.settings.autoScroll = e.target.checked;
412
- this.saveSettings();
413
- });
414
- document.getElementById('connectTimeout')?.addEventListener('change', (e) => {
415
- this.settings.connectTimeout = parseInt(e.target.value) * 1000;
416
- this.saveSettings();
417
- });
418
- }
419
518
 
420
519
  async fetchAgents() {
421
520
  try {
422
- const res = await fetch(BASE_URL + '/api/agents');
521
+ const res = await this.apiFetch(BASE_URL + '/api/agents');
423
522
  const data = await res.json();
424
523
  if (data.agents) {
425
524
  data.agents.forEach(a => this.agents.set(a.id, a));
@@ -432,7 +531,7 @@ class GMGUIApp {
432
531
  async fetchConversations() {
433
532
  try {
434
533
  console.log('[DEBUG] fetchConversations: Starting fetch from', BASE_URL + '/api/conversations');
435
- const res = await fetch(BASE_URL + '/api/conversations');
534
+ const res = await this.apiFetch(BASE_URL + '/api/conversations');
436
535
  console.log('[DEBUG] fetchConversations: Response status:', res.status);
437
536
 
438
537
  if (!res.ok) {
@@ -470,7 +569,7 @@ class GMGUIApp {
470
569
 
471
570
  async fetchMessages(conversationId) {
472
571
  try {
473
- const res = await fetch(`${BASE_URL}/api/conversations/${conversationId}/messages`);
572
+ const res = await this.apiFetch(`${BASE_URL}/api/conversations/${conversationId}/messages`);
474
573
  const data = await res.json();
475
574
  return data.messages || [];
476
575
  } catch (e) {
@@ -589,7 +688,7 @@ class GMGUIApp {
589
688
 
590
689
  async deleteConversation(id) {
591
690
  try {
592
- const res = await fetch(`${BASE_URL}/api/conversations/${id}`, { method: 'DELETE' });
691
+ const res = await this.apiFetch(`${BASE_URL}/api/conversations/${id}`, { method: 'DELETE' });
593
692
  if (!res.ok) {
594
693
  console.error('deleteConversation failed:', res.status);
595
694
  return;
@@ -745,25 +844,129 @@ class GMGUIApp {
745
844
  return elements.length > 0 ? elements : null;
746
845
  }
747
846
 
748
- renderTextOrHtml(text) {
749
- if (this.looksLikeHtml(text)) {
750
- return this.createSandboxedHtml(text);
751
- }
752
- const bubble = document.createElement('div');
753
- bubble.className = 'message-bubble';
754
- bubble.textContent = text;
755
- return bubble;
756
- }
757
-
758
- createSandboxedHtml(rawHtml) {
759
- const wrap = document.createElement('div');
760
- wrap.className = 'html-block rendered-html';
761
- const content = document.createElement('div');
762
- content.className = 'html-content';
763
- content.innerHTML = this.sanitizeHtml(rawHtml);
764
- wrap.appendChild(content);
765
- return wrap;
766
- }
847
+ renderTextOrHtml(text) {
848
+ if (this.looksLikeHtml(text)) {
849
+ return this.createSandboxedHtml(text);
850
+ }
851
+
852
+ // CRITICAL FIX: Don't bundle all text into one bubble
853
+ // Try splitting by paragraph breaks first (double newlines)
854
+ let parts = text.split('\n\n').filter(p => p.trim());
855
+
856
+ // If no paragraphs found, try splitting by single newlines
857
+ // (handles imported messages that may not have proper paragraph breaks)
858
+ if (parts.length === 1) {
859
+ const singleNewlines = text.split('\n').filter(p => p.trim());
860
+ // Only use single newlines if we get reasonable chunks (3+ non-empty lines)
861
+ if (singleNewlines.length >= 3) {
862
+ parts = singleNewlines;
863
+ }
864
+ }
865
+
866
+ // If still just one part and it's very long (>500 chars), split by sentences
867
+ if (parts.length === 1 && text.length > 500) {
868
+ const sentences = text.match(/[^.!?]+[.!?]+/g) || [text];
869
+ if (sentences.length > 1) {
870
+ parts = sentences.map(s => s.trim()).filter(s => s);
871
+ }
872
+ }
873
+
874
+ if (parts.length === 1) {
875
+ // Single item - just one bubble
876
+ const bubble = document.createElement('div');
877
+ bubble.className = 'message-bubble';
878
+ bubble.textContent = text;
879
+ return bubble;
880
+ }
881
+
882
+ // Multiple parts - create separate bubbles for each
883
+ const container = document.createElement('div');
884
+ container.className = 'message-bubbles-container';
885
+
886
+ for (const part of parts) {
887
+ const bubble = document.createElement('div');
888
+ bubble.className = 'message-bubble';
889
+ bubble.textContent = part;
890
+ container.appendChild(bubble);
891
+ }
892
+
893
+ return container;
894
+ }
895
+
896
+ createSandboxedHtml(rawHtml) {
897
+ const wrap = document.createElement('div');
898
+ wrap.className = 'html-block rendered-html';
899
+ const content = document.createElement('div');
900
+ content.className = 'html-content';
901
+
902
+ // Get current theme to apply to HTML content
903
+ const currentTheme = document.documentElement.getAttribute('data-theme') ||
904
+ (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
905
+
906
+ // CRITICAL: Inject theme-aware CSS to ensure text colors work in dark/light mode
907
+ const themeCSS = currentTheme === 'dark'
908
+ ? `<style>
909
+ .html-content {
910
+ color: #f8fafc;
911
+ background: transparent;
912
+ }
913
+ .html-content p { color: #cbd5e1; }
914
+ .html-content h1, .html-content h2, .html-content h3,
915
+ .html-content h4, .html-content h5, .html-content h6 {
916
+ color: #f8fafc;
917
+ }
918
+ .html-content a { color: #6366f1; }
919
+ .html-content code { color: #c7d2fe; background: rgba(0,0,0,0.3); }
920
+ .html-content pre { background: rgba(0,0,0,0.5); color: #e0e7ff; }
921
+ .html-content table { border-color: #334155; }
922
+ .html-content th { background: #1a202c; color: #f8fafc; }
923
+ .html-content td { border-color: #334155; }
924
+ .html-content blockquote { border-color: #334155; color: #cbd5e1; }
925
+ .html-content ul, .html-content ol { color: #cbd5e1; }
926
+ .html-content li { color: #cbd5e1; }
927
+ </style>`
928
+ : `<style>
929
+ .html-content {
930
+ color: #1d2129;
931
+ background: transparent;
932
+ }
933
+ .html-content p { color: #475569; }
934
+ .html-content h1, .html-content h2, .html-content h3,
935
+ .html-content h4, .html-content h5, .html-content h6 {
936
+ color: #1d2129;
937
+ }
938
+ .html-content a { color: #4f46e5; }
939
+ .html-content code { color: #6366f1; background: rgba(99,102,241,0.1); }
940
+ .html-content pre { background: #f3f4f6; color: #1d2129; }
941
+ .html-content table { border-color: #e5e7eb; }
942
+ .html-content th { background: #f9fafb; color: #1d2129; }
943
+ .html-content td { border-color: #e5e7eb; }
944
+ .html-content blockquote { border-color: #e5e7eb; color: #475569; }
945
+ .html-content ul, .html-content ol { color: #475569; }
946
+ .html-content li { color: #475569; }
947
+ </style>`;
948
+
949
+ // CRITICAL: Ensure RippleUI styles are available for agent HTML
950
+ // Agent responses use RippleUI/Tailwind classes, so wrap in a context that has those styles
951
+ let enhancedHtml = themeCSS + rawHtml;
952
+
953
+ // If HTML doesn't already have the RippleUI wrapper classes, add them
954
+ if (!rawHtml.includes('space-y-4') && !rawHtml.includes('card') && !rawHtml.includes('alert')) {
955
+ // Wrap in RippleUI container if agent didn't already wrap it
956
+ enhancedHtml = themeCSS + `<div class="space-y-4 p-6 max-w-4xl">${rawHtml}</div>`;
957
+ console.log('[HTML] Wrapped agent HTML in RippleUI container with theme CSS');
958
+ } else {
959
+ console.log('[HTML] Agent HTML already has RippleUI classes, applying theme CSS');
960
+ }
961
+
962
+ content.innerHTML = this.sanitizeHtml(enhancedHtml);
963
+ wrap.appendChild(content);
964
+
965
+ // Apply theme attribute to content so nested elements inherit
966
+ content.setAttribute('data-theme', currentTheme);
967
+
968
+ return wrap;
969
+ }
767
970
 
768
971
  addMessageToDisplay(msg) {
769
972
  const div = document.getElementById('chatMessages');
@@ -810,30 +1013,40 @@ class GMGUIApp {
810
1013
  });
811
1014
  }
812
1015
 
813
- // Display segmented content if available
814
- if (msg.content.segments && Array.isArray(msg.content.segments)) {
815
- console.log('[HTML] Rendering segments from agent response');
816
- msg.content.segments.forEach(segment => {
817
- el.appendChild(this.renderSegment(segment));
818
- });
819
- } else if (msg.content.text && !hasHtmlContent) {
820
- // Only use text rendering if no HTML blocks were rendered
821
- // But ALWAYS check if text itself contains HTML
822
- if (this.looksLikeHtml(msg.content.text)) {
823
- console.log('[HTML] Agent text content contains HTML - rendering as HTML');
824
- el.appendChild(this.createSandboxedHtml(msg.content.text));
825
- } else {
826
- const parsed = this.parseAndRenderContent(msg.content.text);
827
- if (parsed) {
828
- parsed.forEach(elem => el.appendChild(elem));
829
- } else {
830
- const bubble = document.createElement('div');
831
- bubble.className = 'message-bubble';
832
- bubble.textContent = msg.content.text;
833
- el.appendChild(bubble);
834
- }
835
- }
836
- }
1016
+ // CRITICAL: Agent responses are now HTML from system prompt
1017
+ // Check if we have text first (which should be HTML)
1018
+ if (msg.content.text && !hasHtmlContent) {
1019
+ // ALWAYS check if text itself contains HTML first
1020
+ if (this.looksLikeHtml(msg.content.text)) {
1021
+ console.log('[HTML] ✅ Agent response is HTML - rendering directly (NOT segmenting)');
1022
+ el.appendChild(this.createSandboxedHtml(msg.content.text));
1023
+ } else {
1024
+ // Only if NOT HTML, then try segmenting
1025
+ console.log('[HTML] Text is not HTML, attempting segmentation');
1026
+ if (msg.content.segments && Array.isArray(msg.content.segments)) {
1027
+ console.log('[HTML] Rendering', msg.content.segments.length, 'segments');
1028
+ msg.content.segments.forEach(segment => {
1029
+ el.appendChild(this.renderSegment(segment));
1030
+ });
1031
+ } else {
1032
+ const parsed = this.parseAndRenderContent(msg.content.text);
1033
+ if (parsed) {
1034
+ parsed.forEach(elem => el.appendChild(elem));
1035
+ } else {
1036
+ const bubble = document.createElement('div');
1037
+ bubble.className = 'message-bubble';
1038
+ bubble.textContent = msg.content.text;
1039
+ el.appendChild(bubble);
1040
+ }
1041
+ }
1042
+ }
1043
+ } else if (msg.content.segments && Array.isArray(msg.content.segments) && !hasHtmlContent) {
1044
+ // Fallback: only use segments if we have them and no text
1045
+ console.log('[HTML] No text content, rendering segments');
1046
+ msg.content.segments.forEach(segment => {
1047
+ el.appendChild(this.renderSegment(segment));
1048
+ });
1049
+ }
837
1050
 
838
1051
  // Display metadata if available
839
1052
  if (msg.content.metadata) {
@@ -1041,7 +1254,7 @@ class GMGUIApp {
1041
1254
  ? folderPath.split('/').pop() || folderPath
1042
1255
  : `Chat ${this.conversations.size + 1}`;
1043
1256
  try {
1044
- const res = await fetch(BASE_URL + '/api/conversations', {
1257
+ const res = await this.apiFetch(BASE_URL + '/api/conversations', {
1045
1258
  method: 'POST',
1046
1259
  headers: { 'Content-Type': 'application/json' },
1047
1260
  body: JSON.stringify({ agentId: this.selectedAgent || 'claude-code', title }),
@@ -1082,7 +1295,7 @@ class GMGUIApp {
1082
1295
 
1083
1296
  try {
1084
1297
  const folderPath = conv?.folderPath || localStorage.getItem('gmgui-home') || '/config';
1085
- const res = await fetch(`${BASE_URL}/api/conversations/${this.currentConversation}/messages`, {
1298
+ const res = await this.apiFetch(`${BASE_URL}/api/conversations/${this.currentConversation}/messages`, {
1086
1299
  method: 'POST',
1087
1300
  headers: { 'Content-Type': 'application/json' },
1088
1301
  body: JSON.stringify({
@@ -1128,7 +1341,7 @@ class GMGUIApp {
1128
1341
 
1129
1342
  this.pollingInterval = setInterval(async () => {
1130
1343
  try {
1131
- const res = await fetch(`${BASE_URL}/api/conversations/${conversationId}/messages`);
1344
+ const res = await this.apiFetch(`${BASE_URL}/api/conversations/${conversationId}/messages`);
1132
1345
  const data = await res.json();
1133
1346
  const messages = data.messages || [];
1134
1347
 
@@ -1295,7 +1508,7 @@ class GMGUIApp {
1295
1508
  if (!list) return;
1296
1509
  list.innerHTML = '<div style="padding: 1rem; color: var(--text-tertiary);">Loading...</div>';
1297
1510
  try {
1298
- const res = await fetch(BASE_URL + '/api/folders', {
1511
+ const res = await this.apiFetch(BASE_URL + '/api/folders', {
1299
1512
  method: 'POST',
1300
1513
  headers: { 'Content-Type': 'application/json' },
1301
1514
  body: JSON.stringify({ path: folderPath }),
@@ -1377,7 +1590,7 @@ function createChatInFolder() {
1377
1590
  async function importClaudeCodeConversations() {
1378
1591
  closeNewChatModal();
1379
1592
  try {
1380
- const res = await fetch(BASE_URL + '/api/import/claude-code');
1593
+ const res = await this.apiFetch(BASE_URL + '/api/import/claude-code');
1381
1594
  const data = await res.json();
1382
1595
 
1383
1596
  if (!data.imported) {
package/static/index.html CHANGED
@@ -130,13 +130,6 @@
130
130
  <div class="chat-option-desc">Contextualize chat to a specific folder</div>
131
131
  </div>
132
132
  </button>
133
- <button class="chat-option-btn" onclick="importClaudeCodeConversations()">
134
- <span class="chat-option-icon">📥</span>
135
- <div class="chat-option-content">
136
- <div class="chat-option-title">Import Claude Code conversations</div>
137
- <div class="chat-option-desc">Load existing conversations from Claude Code</div>
138
- </div>
139
- </button>
140
133
  </div>
141
134
  </div>
142
135
  </div>