claude-code-templates 1.11.0 โ 1.12.0
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/package.json +1 -2
- package/src/analytics/core/ConversationAnalyzer.js +65 -23
- package/src/analytics-web/components/AgentsPage.js +2365 -156
- package/src/analytics-web/components/App.js +11 -0
- package/src/analytics-web/components/DashboardPage.js +4 -0
- package/src/analytics-web/components/ToolDisplay.js +17 -2
- package/src/analytics-web/index.html +3005 -1059
- package/src/analytics.js +342 -3
|
@@ -37,6 +37,10 @@ class AgentsPage {
|
|
|
37
37
|
this.loadedConversations = [];
|
|
38
38
|
this.loadedMessages = new Map(); // Cache messages by conversation ID (now stores paginated data)
|
|
39
39
|
|
|
40
|
+
// Agent data
|
|
41
|
+
this.agents = [];
|
|
42
|
+
this.selectedAgentId = null;
|
|
43
|
+
|
|
40
44
|
// State transition tracking for enhanced user experience
|
|
41
45
|
this.lastMessageTime = new Map(); // Track when last message was received per conversation
|
|
42
46
|
|
|
@@ -68,6 +72,7 @@ class AgentsPage {
|
|
|
68
72
|
this.stateService.setLoading(true);
|
|
69
73
|
await this.render();
|
|
70
74
|
await this.initializeComponents();
|
|
75
|
+
await this.loadAgentsData();
|
|
71
76
|
await this.loadConversationsData();
|
|
72
77
|
this.isInitialized = true;
|
|
73
78
|
} catch (error) {
|
|
@@ -546,39 +551,6 @@ class AgentsPage {
|
|
|
546
551
|
}, 5000);
|
|
547
552
|
}
|
|
548
553
|
|
|
549
|
-
/**
|
|
550
|
-
* Test console interaction functionality (for development)
|
|
551
|
-
*/
|
|
552
|
-
testConsoleInteraction() {
|
|
553
|
-
// Test choice-based interaction (like your example)
|
|
554
|
-
const testChoiceInteraction = {
|
|
555
|
-
id: 'test-choice-' + Date.now(),
|
|
556
|
-
type: 'choice',
|
|
557
|
-
tool: 'Search',
|
|
558
|
-
description: 'Search(pattern: "(?:Yes|No|yes|no)(?:,\\s*and\\s*don\'t\\s*ask\\s*again)?", path: "../../../../../../../.claude/projects/-Users-danipower-Proyectos-Github-claude-code-templates", include: "*.jsonl")',
|
|
559
|
-
prompt: 'Do you want to proceed?',
|
|
560
|
-
options: [
|
|
561
|
-
'Yes',
|
|
562
|
-
'Yes, and add /Users/danipower/.claude/projects/-Users-danipower-Proyectos-Github-claude-code-templates as a working directory for this session',
|
|
563
|
-
'No, and tell Claude what to do differently'
|
|
564
|
-
]
|
|
565
|
-
};
|
|
566
|
-
|
|
567
|
-
// Test text input interaction
|
|
568
|
-
const testTextInteraction = {
|
|
569
|
-
id: 'test-text-' + Date.now(),
|
|
570
|
-
type: 'text',
|
|
571
|
-
tool: 'Console Input',
|
|
572
|
-
description: 'Claude Code is requesting text input from the console.',
|
|
573
|
-
prompt: 'Please provide your input:'
|
|
574
|
-
};
|
|
575
|
-
|
|
576
|
-
// Randomly choose which type to test, or ask user
|
|
577
|
-
const testType = Math.random() > 0.5 ? testChoiceInteraction : testTextInteraction;
|
|
578
|
-
|
|
579
|
-
console.log('๐งช Testing console interaction:', testType);
|
|
580
|
-
this.showConsoleInteraction(testType);
|
|
581
|
-
}
|
|
582
554
|
|
|
583
555
|
/**
|
|
584
556
|
* Update conversation state elements in the DOM
|
|
@@ -663,6 +635,36 @@ class AgentsPage {
|
|
|
663
635
|
</div>
|
|
664
636
|
</div>
|
|
665
637
|
|
|
638
|
+
<!-- Agents Section -->
|
|
639
|
+
<div class="agents-section">
|
|
640
|
+
<div class="agents-header">
|
|
641
|
+
<h4>Available Agents</h4>
|
|
642
|
+
<div class="agents-info">
|
|
643
|
+
<span class="agents-count" id="agents-count">0 agents</span>
|
|
644
|
+
<button class="refresh-agents-btn" id="refresh-agents" title="Refresh agents">
|
|
645
|
+
<span class="btn-icon">๐</span>
|
|
646
|
+
</button>
|
|
647
|
+
</div>
|
|
648
|
+
</div>
|
|
649
|
+
|
|
650
|
+
<div class="agents-list" id="agents-list">
|
|
651
|
+
<!-- Agent items will be rendered here -->
|
|
652
|
+
</div>
|
|
653
|
+
|
|
654
|
+
<!-- Loading state for agents -->
|
|
655
|
+
<div class="agents-loading" id="agents-loading" style="display: none;">
|
|
656
|
+
<div class="loading-spinner"></div>
|
|
657
|
+
<span class="loading-text">Loading agents...</span>
|
|
658
|
+
</div>
|
|
659
|
+
|
|
660
|
+
<!-- Empty state for agents -->
|
|
661
|
+
<div class="agents-empty" id="agents-empty" style="display: none;">
|
|
662
|
+
<div class="empty-icon">๐ค</div>
|
|
663
|
+
<p>No agents found</p>
|
|
664
|
+
<small>Create agents in your .claude/agents directory to see them here</small>
|
|
665
|
+
</div>
|
|
666
|
+
</div>
|
|
667
|
+
|
|
666
668
|
<!-- Loading State -->
|
|
667
669
|
<div class="loading-state" id="conversations-loading" style="display: none;">
|
|
668
670
|
<div class="loading-spinner"></div>
|
|
@@ -715,162 +717,2210 @@ class AgentsPage {
|
|
|
715
717
|
</div>
|
|
716
718
|
</div>
|
|
717
719
|
|
|
718
|
-
<!-- Two Column Layout -->
|
|
719
|
-
<div class="conversations-layout">
|
|
720
|
-
<!-- Left Sidebar: Conversations List -->
|
|
721
|
-
<div class="conversations-sidebar">
|
|
722
|
-
<div class="sidebar-header">
|
|
723
|
-
<h3>Chats</h3>
|
|
724
|
-
<span class="conversation-count" id="sidebar-count">0</span>
|
|
725
|
-
</div>
|
|
726
|
-
<div class="conversations-list" id="conversations-list">
|
|
727
|
-
<!-- Conversation items will be rendered here -->
|
|
728
|
-
</div>
|
|
729
|
-
|
|
730
|
-
<!-- Load More Indicator -->
|
|
731
|
-
<div class="load-more-indicator" id="load-more-indicator" style="display: none;">
|
|
732
|
-
<div class="loading-spinner"></div>
|
|
733
|
-
<span class="loading-text">Loading more conversations...</span>
|
|
720
|
+
<!-- Two Column Layout -->
|
|
721
|
+
<div class="conversations-layout">
|
|
722
|
+
<!-- Left Sidebar: Conversations List -->
|
|
723
|
+
<div class="conversations-sidebar">
|
|
724
|
+
<div class="sidebar-header">
|
|
725
|
+
<h3>Chats</h3>
|
|
726
|
+
<span class="conversation-count" id="sidebar-count">0</span>
|
|
727
|
+
</div>
|
|
728
|
+
<div class="conversations-list" id="conversations-list">
|
|
729
|
+
<!-- Conversation items will be rendered here -->
|
|
730
|
+
</div>
|
|
731
|
+
|
|
732
|
+
<!-- Load More Indicator -->
|
|
733
|
+
<div class="load-more-indicator" id="load-more-indicator" style="display: none;">
|
|
734
|
+
<div class="loading-spinner"></div>
|
|
735
|
+
<span class="loading-text">Loading more conversations...</span>
|
|
736
|
+
</div>
|
|
737
|
+
</div>
|
|
738
|
+
|
|
739
|
+
<!-- Right Panel: Messages Detail -->
|
|
740
|
+
<div class="messages-panel">
|
|
741
|
+
<div class="messages-header" id="messages-header">
|
|
742
|
+
<div class="selected-conversation-info">
|
|
743
|
+
<h3 id="selected-conversation-title">Select a chat</h3>
|
|
744
|
+
<div class="selected-conversation-meta" id="selected-conversation-meta"></div>
|
|
745
|
+
</div>
|
|
746
|
+
<div class="messages-actions">
|
|
747
|
+
<button class="action-btn-small" id="export-conversation" title="Export conversation">
|
|
748
|
+
<span class="btn-icon-small">๐</span>
|
|
749
|
+
Export
|
|
750
|
+
</button>
|
|
751
|
+
</div>
|
|
752
|
+
</div>
|
|
753
|
+
|
|
754
|
+
<div class="messages-content" id="messages-content">
|
|
755
|
+
<div class="no-conversation-selected">
|
|
756
|
+
<div class="no-selection-icon">๐ฌ</div>
|
|
757
|
+
<h4>No conversation selected</h4>
|
|
758
|
+
<p>Choose a conversation from the sidebar to view its messages</p>
|
|
759
|
+
</div>
|
|
760
|
+
</div>
|
|
761
|
+
|
|
762
|
+
<!-- Conversation State Banner -->
|
|
763
|
+
<div class="conversation-state-banner" id="conversation-state-banner" style="display: none;">
|
|
764
|
+
<div class="state-indicator">
|
|
765
|
+
<span class="state-dot" id="state-dot"></span>
|
|
766
|
+
<span class="state-text" id="state-text">Ready</span>
|
|
767
|
+
</div>
|
|
768
|
+
<div class="state-timestamp" id="state-timestamp"></div>
|
|
769
|
+
</div>
|
|
770
|
+
</div>
|
|
771
|
+
</div>
|
|
772
|
+
|
|
773
|
+
<!-- Empty State -->
|
|
774
|
+
<div class="empty-state" id="empty-state" style="display: none;">
|
|
775
|
+
<div class="empty-content">
|
|
776
|
+
<span class="empty-icon">๐ฌ</span>
|
|
777
|
+
<h3>No conversations found</h3>
|
|
778
|
+
<p>No agent conversations match your current filters.</p>
|
|
779
|
+
<button class="empty-action" id="clear-filters">Clear Filters</button>
|
|
780
|
+
</div>
|
|
781
|
+
</div>
|
|
782
|
+
</div>
|
|
783
|
+
`;
|
|
784
|
+
|
|
785
|
+
this.bindEvents();
|
|
786
|
+
this.setupInfiniteScroll();
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* Initialize child components
|
|
791
|
+
*/
|
|
792
|
+
async initializeComponents() {
|
|
793
|
+
// Initialize ConversationTable for detailed view if available
|
|
794
|
+
const tableContainer = this.container.querySelector('#conversations-table');
|
|
795
|
+
if (tableContainer && typeof ConversationTable !== 'undefined') {
|
|
796
|
+
try {
|
|
797
|
+
this.components.conversationTable = new ConversationTable(
|
|
798
|
+
tableContainer,
|
|
799
|
+
this.dataService,
|
|
800
|
+
this.stateService
|
|
801
|
+
);
|
|
802
|
+
await this.components.conversationTable.initialize();
|
|
803
|
+
} catch (error) {
|
|
804
|
+
console.warn('ConversationTable initialization failed:', error);
|
|
805
|
+
// Show fallback content
|
|
806
|
+
tableContainer.innerHTML = `
|
|
807
|
+
<div class="conversation-table-placeholder">
|
|
808
|
+
<p>Detailed table view not available</p>
|
|
809
|
+
</div>
|
|
810
|
+
`;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* Bind event listeners
|
|
817
|
+
*/
|
|
818
|
+
bindEvents() {
|
|
819
|
+
// Filter controls
|
|
820
|
+
const statusFilter = this.container.querySelector('#status-filter');
|
|
821
|
+
statusFilter.addEventListener('change', (e) => this.updateFilter('status', e.target.value));
|
|
822
|
+
|
|
823
|
+
const timeFilter = this.container.querySelector('#time-filter');
|
|
824
|
+
timeFilter.addEventListener('change', (e) => this.updateFilter('timeRange', e.target.value));
|
|
825
|
+
|
|
826
|
+
const searchInput = this.container.querySelector('#search-filter');
|
|
827
|
+
searchInput.addEventListener('input', (e) => this.updateFilter('search', e.target.value));
|
|
828
|
+
|
|
829
|
+
const clearSearch = this.container.querySelector('#clear-search');
|
|
830
|
+
clearSearch.addEventListener('click', () => this.clearSearch());
|
|
831
|
+
|
|
832
|
+
// Error retry
|
|
833
|
+
const retryBtn = this.container.querySelector('#retry-load');
|
|
834
|
+
if (retryBtn) {
|
|
835
|
+
retryBtn.addEventListener('click', () => this.loadConversationsData());
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// Clear filters
|
|
839
|
+
const clearFiltersBtn = this.container.querySelector('#clear-filters');
|
|
840
|
+
if (clearFiltersBtn) {
|
|
841
|
+
clearFiltersBtn.addEventListener('click', () => this.clearAllFilters());
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
// Refresh agents
|
|
846
|
+
const refreshAgentsBtn = this.container.querySelector('#refresh-agents');
|
|
847
|
+
if (refreshAgentsBtn) {
|
|
848
|
+
refreshAgentsBtn.addEventListener('click', () => this.refreshAgents());
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* Setup infinite scroll for conversations list
|
|
854
|
+
*/
|
|
855
|
+
setupInfiniteScroll() {
|
|
856
|
+
const conversationsContainer = this.container.querySelector('#conversations-list');
|
|
857
|
+
if (!conversationsContainer) return;
|
|
858
|
+
|
|
859
|
+
conversationsContainer.addEventListener('scroll', () => {
|
|
860
|
+
const { scrollTop, scrollHeight, clientHeight } = conversationsContainer;
|
|
861
|
+
const threshold = 100; // Load more when 100px from bottom
|
|
862
|
+
|
|
863
|
+
if (scrollHeight - scrollTop - clientHeight < threshold) {
|
|
864
|
+
this.loadMoreConversations();
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* Update loading indicator
|
|
871
|
+
* @param {boolean} isLoading - Whether to show loading indicator
|
|
872
|
+
*/
|
|
873
|
+
updateLoadingIndicator(isLoading) {
|
|
874
|
+
const loadingIndicator = this.container.querySelector('#load-more-indicator');
|
|
875
|
+
if (loadingIndicator) {
|
|
876
|
+
loadingIndicator.style.display = isLoading ? 'flex' : 'none';
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/**
|
|
881
|
+
* Load agents data from API
|
|
882
|
+
*/
|
|
883
|
+
async loadAgentsData() {
|
|
884
|
+
try {
|
|
885
|
+
this.showAgentsLoading(true);
|
|
886
|
+
|
|
887
|
+
const agentsData = await this.dataService.cachedFetch('/api/agents');
|
|
888
|
+
|
|
889
|
+
if (agentsData && agentsData.agents) {
|
|
890
|
+
this.agents = agentsData.agents;
|
|
891
|
+
this.renderAgents();
|
|
892
|
+
} else {
|
|
893
|
+
this.showAgentsEmpty();
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
} catch (error) {
|
|
897
|
+
console.error('Error loading agents data:', error);
|
|
898
|
+
this.showAgentsEmpty();
|
|
899
|
+
} finally {
|
|
900
|
+
this.showAgentsLoading(false);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* Render global agents in the agents list (user-level only)
|
|
906
|
+
*/
|
|
907
|
+
renderAgents() {
|
|
908
|
+
const agentsList = this.container.querySelector('#agents-list');
|
|
909
|
+
const agentsCount = this.container.querySelector('#agents-count');
|
|
910
|
+
|
|
911
|
+
if (!agentsList || !agentsCount) return;
|
|
912
|
+
|
|
913
|
+
// Filter only global/user agents for main section
|
|
914
|
+
const globalAgents = this.agents.filter(agent => agent.level === 'user');
|
|
915
|
+
|
|
916
|
+
if (globalAgents.length === 0) {
|
|
917
|
+
this.showAgentsEmpty();
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// Update count for global agents only
|
|
922
|
+
agentsCount.textContent = `${globalAgents.length} global agent${globalAgents.length !== 1 ? 's' : ''}`;
|
|
923
|
+
|
|
924
|
+
// Render global agent items (compact rectangles)
|
|
925
|
+
const agentsHTML = globalAgents.map(agent => {
|
|
926
|
+
const levelBadge = agent.level === 'project' ? 'P' : 'U';
|
|
927
|
+
|
|
928
|
+
return `
|
|
929
|
+
<div class="agent-item" data-agent-id="${agent.name}">
|
|
930
|
+
<div class="agent-dot" style="background-color: ${agent.color}"></div>
|
|
931
|
+
<span class="agent-name">${agent.name}</span>
|
|
932
|
+
<span class="agent-level-badge ${agent.level}" title="${agent.level === 'project' ? 'Project Agent' : 'User Agent'}">${levelBadge}</span>
|
|
933
|
+
</div>
|
|
934
|
+
`;
|
|
935
|
+
}).join('');
|
|
936
|
+
|
|
937
|
+
agentsList.innerHTML = agentsHTML;
|
|
938
|
+
|
|
939
|
+
// Hide empty state and show list
|
|
940
|
+
this.hideAgentsEmpty();
|
|
941
|
+
agentsList.style.display = 'block';
|
|
942
|
+
|
|
943
|
+
// Bind agent events
|
|
944
|
+
this.bindAgentEvents();
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Bind events for agent items
|
|
949
|
+
*/
|
|
950
|
+
bindAgentEvents() {
|
|
951
|
+
const agentItems = this.container.querySelectorAll('.agent-item');
|
|
952
|
+
|
|
953
|
+
agentItems.forEach(item => {
|
|
954
|
+
item.addEventListener('click', () => {
|
|
955
|
+
const agentId = item.dataset.agentId;
|
|
956
|
+
this.selectAgent(agentId);
|
|
957
|
+
});
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/**
|
|
962
|
+
* Select an agent (opens modal with details)
|
|
963
|
+
* @param {string} agentId - Agent ID
|
|
964
|
+
*/
|
|
965
|
+
selectAgent(agentId) {
|
|
966
|
+
const agent = this.agents.find(a => a.name === agentId);
|
|
967
|
+
if (agent) {
|
|
968
|
+
this.openAgentModal(agent);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Open agent details modal
|
|
974
|
+
* @param {Object} agent - Agent object
|
|
975
|
+
*/
|
|
976
|
+
openAgentModal(agent) {
|
|
977
|
+
// If this is a specific tool, open the custom tool modal
|
|
978
|
+
if (agent.isToolDetails) {
|
|
979
|
+
this.openToolModal(agent);
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
const modalHTML = `
|
|
984
|
+
<div class="agent-modal-overlay" id="agent-modal-overlay">
|
|
985
|
+
<div class="agent-modal">
|
|
986
|
+
<div class="agent-modal-header">
|
|
987
|
+
<div class="agent-modal-title">
|
|
988
|
+
<div class="agent-title-main">
|
|
989
|
+
<div class="agent-dot" style="background-color: ${agent.color}"></div>
|
|
990
|
+
<div class="agent-title-info">
|
|
991
|
+
<h3>${agent.name}</h3>
|
|
992
|
+
<div class="agent-subtitle">
|
|
993
|
+
<span class="agent-level-badge ${agent.level}">${agent.level === 'project' ? 'Project Agent' : 'User Agent'}</span>
|
|
994
|
+
${agent.projectName ? `<span class="agent-project-name">โข ${agent.projectName}</span>` : ''}
|
|
995
|
+
</div>
|
|
996
|
+
</div>
|
|
997
|
+
</div>
|
|
998
|
+
</div>
|
|
999
|
+
<button class="agent-modal-close" id="agent-modal-close">×</button>
|
|
1000
|
+
</div>
|
|
1001
|
+
|
|
1002
|
+
<div class="agent-modal-content">
|
|
1003
|
+
<div class="agent-info-section">
|
|
1004
|
+
<h4>Description</h4>
|
|
1005
|
+
<p>${agent.description}</p>
|
|
1006
|
+
</div>
|
|
1007
|
+
|
|
1008
|
+
${agent.projectName ? `
|
|
1009
|
+
<div class="agent-info-section">
|
|
1010
|
+
<h4>Project</h4>
|
|
1011
|
+
<p>${agent.projectName}</p>
|
|
1012
|
+
</div>
|
|
1013
|
+
` : ''}
|
|
1014
|
+
|
|
1015
|
+
<div class="agent-info-section">
|
|
1016
|
+
<h4>Tools Access</h4>
|
|
1017
|
+
<p>${agent.tools && agent.tools.length > 0
|
|
1018
|
+
? `Has access to: ${agent.tools.join(', ')}`
|
|
1019
|
+
: 'Has access to all available tools'}</p>
|
|
1020
|
+
</div>
|
|
1021
|
+
|
|
1022
|
+
<div class="agent-info-section">
|
|
1023
|
+
<h4>System Prompt</h4>
|
|
1024
|
+
<div class="agent-system-prompt">${agent.systemPrompt ? agent.systemPrompt.replace(/\n/g, '<br>') : 'No system prompt available'}</div>
|
|
1025
|
+
</div>
|
|
1026
|
+
|
|
1027
|
+
<div class="agent-usage-tips">
|
|
1028
|
+
<h4>๐ก How to Use This Agent</h4>
|
|
1029
|
+
<div class="usage-tips-content">
|
|
1030
|
+
<p><strong>To invoke this agent explicitly:</strong></p>
|
|
1031
|
+
<code class="usage-example">Use the ${agent.name} agent to [describe your request]</code>
|
|
1032
|
+
|
|
1033
|
+
<p><strong>Alternative ways to invoke:</strong></p>
|
|
1034
|
+
<ul>
|
|
1035
|
+
<li><code>Ask the ${agent.name} agent to [task]</code></li>
|
|
1036
|
+
<li><code>Have the ${agent.name} agent [action]</code></li>
|
|
1037
|
+
<li><code>Let the ${agent.name} agent handle [request]</code></li>
|
|
1038
|
+
</ul>
|
|
1039
|
+
|
|
1040
|
+
<p><strong>Best practices:</strong></p>
|
|
1041
|
+
<ul>
|
|
1042
|
+
<li>Be specific about what you want the agent to do</li>
|
|
1043
|
+
<li>Provide context when needed</li>
|
|
1044
|
+
<li>The agent will automatically use appropriate tools</li>
|
|
1045
|
+
</ul>
|
|
1046
|
+
</div>
|
|
1047
|
+
</div>
|
|
1048
|
+
|
|
1049
|
+
<div class="agent-metadata">
|
|
1050
|
+
<small><strong>File:</strong> ${agent.filePath}</small><br>
|
|
1051
|
+
<small><strong>Last modified:</strong> ${new Date(agent.lastModified).toLocaleString()}</small>
|
|
1052
|
+
</div>
|
|
1053
|
+
</div>
|
|
1054
|
+
</div>
|
|
1055
|
+
</div>
|
|
1056
|
+
`;
|
|
1057
|
+
|
|
1058
|
+
// Add modal to DOM
|
|
1059
|
+
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
|
1060
|
+
|
|
1061
|
+
// Bind close events
|
|
1062
|
+
document.getElementById('agent-modal-close').addEventListener('click', () => this.closeAgentModal());
|
|
1063
|
+
document.getElementById('agent-modal-overlay').addEventListener('click', (e) => {
|
|
1064
|
+
if (e.target.id === 'agent-modal-overlay') {
|
|
1065
|
+
this.closeAgentModal();
|
|
1066
|
+
}
|
|
1067
|
+
});
|
|
1068
|
+
|
|
1069
|
+
// ESC key to close - store reference for cleanup
|
|
1070
|
+
this.modalKeydownHandler = (e) => {
|
|
1071
|
+
if (e.key === 'Escape') {
|
|
1072
|
+
this.closeAgentModal();
|
|
1073
|
+
}
|
|
1074
|
+
};
|
|
1075
|
+
document.addEventListener('keydown', this.modalKeydownHandler);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
/**
|
|
1079
|
+
* Open tool-specific modal for any tool
|
|
1080
|
+
* @param {Object} toolData - Tool data object
|
|
1081
|
+
*/
|
|
1082
|
+
openToolModal(toolData) {
|
|
1083
|
+
switch (toolData.name) {
|
|
1084
|
+
case 'Read':
|
|
1085
|
+
this.openReadToolModal(toolData);
|
|
1086
|
+
break;
|
|
1087
|
+
case 'Edit':
|
|
1088
|
+
this.openEditToolModal(toolData);
|
|
1089
|
+
break;
|
|
1090
|
+
case 'Write':
|
|
1091
|
+
this.openWriteToolModal(toolData);
|
|
1092
|
+
break;
|
|
1093
|
+
case 'Bash':
|
|
1094
|
+
this.openBashToolModal(toolData);
|
|
1095
|
+
break;
|
|
1096
|
+
case 'Glob':
|
|
1097
|
+
this.openGlobToolModal(toolData);
|
|
1098
|
+
break;
|
|
1099
|
+
case 'Grep':
|
|
1100
|
+
this.openGrepToolModal(toolData);
|
|
1101
|
+
break;
|
|
1102
|
+
case 'TodoWrite':
|
|
1103
|
+
this.openTodoWriteToolModal(toolData);
|
|
1104
|
+
break;
|
|
1105
|
+
default:
|
|
1106
|
+
// Fallback to generic agent modal for unknown tools
|
|
1107
|
+
this.openAgentModal({...toolData, isToolDetails: false});
|
|
1108
|
+
break;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
/**
|
|
1113
|
+
* Open Read tool specific modal
|
|
1114
|
+
* @param {Object} readToolData - Read tool data
|
|
1115
|
+
*/
|
|
1116
|
+
openReadToolModal(readToolData) {
|
|
1117
|
+
const input = readToolData.input || {};
|
|
1118
|
+
const filePath = input.file_path || 'Unknown file';
|
|
1119
|
+
const fileName = filePath.split('/').pop() || 'Unknown';
|
|
1120
|
+
const fileExtension = fileName.includes('.') ? fileName.split('.').pop().toLowerCase() : 'txt';
|
|
1121
|
+
const offset = input.offset;
|
|
1122
|
+
const limit = input.limit;
|
|
1123
|
+
const toolId = readToolData.id || 'unknown';
|
|
1124
|
+
|
|
1125
|
+
// Analyze file context for project
|
|
1126
|
+
const isConfigFile = ['json', 'yml', 'yaml', 'toml', 'ini', 'conf', 'config'].includes(fileExtension);
|
|
1127
|
+
const isDocFile = ['md', 'txt', 'rst', 'adoc'].includes(fileExtension);
|
|
1128
|
+
const isCodeFile = ['js', 'ts', 'py', 'go', 'rs', 'java', 'cpp', 'c', 'h', 'css', 'html', 'jsx', 'tsx'].includes(fileExtension);
|
|
1129
|
+
const isProjectRoot = fileName.toLowerCase().includes('claude') || fileName.toLowerCase().includes('readme') || fileName.toLowerCase().includes('package');
|
|
1130
|
+
const isTestFile = fileName.toLowerCase().includes('test') || fileName.toLowerCase().includes('spec');
|
|
1131
|
+
|
|
1132
|
+
let fileCategory = '';
|
|
1133
|
+
let filePurpose = '';
|
|
1134
|
+
let contextIcon = '๐';
|
|
1135
|
+
|
|
1136
|
+
if (isProjectRoot) {
|
|
1137
|
+
fileCategory = 'Project Documentation';
|
|
1138
|
+
filePurpose = 'Understanding project structure and setup';
|
|
1139
|
+
contextIcon = '๐';
|
|
1140
|
+
} else if (fileName.toLowerCase().includes('claude')) {
|
|
1141
|
+
fileCategory = 'Claude Configuration';
|
|
1142
|
+
filePurpose = 'Reading project instructions for AI assistant';
|
|
1143
|
+
contextIcon = '๐ค';
|
|
1144
|
+
} else if (isConfigFile) {
|
|
1145
|
+
fileCategory = 'Configuration File';
|
|
1146
|
+
filePurpose = 'Understanding project settings and dependencies';
|
|
1147
|
+
contextIcon = 'โ๏ธ';
|
|
1148
|
+
} else if (isDocFile) {
|
|
1149
|
+
fileCategory = 'Documentation';
|
|
1150
|
+
filePurpose = 'Reading project documentation or specifications';
|
|
1151
|
+
contextIcon = '๐';
|
|
1152
|
+
} else if (isTestFile) {
|
|
1153
|
+
fileCategory = 'Test File';
|
|
1154
|
+
filePurpose = 'Analyzing test cases and specifications';
|
|
1155
|
+
contextIcon = '๐งช';
|
|
1156
|
+
} else if (isCodeFile) {
|
|
1157
|
+
fileCategory = 'Source Code';
|
|
1158
|
+
filePurpose = 'Analyzing implementation and logic';
|
|
1159
|
+
contextIcon = '๐ป';
|
|
1160
|
+
} else {
|
|
1161
|
+
fileCategory = 'Project File';
|
|
1162
|
+
filePurpose = 'Reading project-related content';
|
|
1163
|
+
contextIcon = '๐';
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// Get directory context
|
|
1167
|
+
const pathParts = filePath.split('/');
|
|
1168
|
+
const projectContext = pathParts.includes('claude-code-templates') ? 'Claude Code Templates Project' : 'Current Project';
|
|
1169
|
+
const relativeDir = pathParts.slice(-3, -1).join('/') || 'root';
|
|
1170
|
+
|
|
1171
|
+
const modalHTML = `
|
|
1172
|
+
<div class="agent-modal-overlay" id="agent-modal-overlay">
|
|
1173
|
+
<div class="agent-modal read-tool-modal">
|
|
1174
|
+
<div class="agent-modal-header">
|
|
1175
|
+
<div class="agent-modal-title">
|
|
1176
|
+
<div class="agent-title-main">
|
|
1177
|
+
<div class="tool-icon read-tool">
|
|
1178
|
+
<span style="font-size: 20px;">${contextIcon}</span>
|
|
1179
|
+
</div>
|
|
1180
|
+
<div class="agent-title-info">
|
|
1181
|
+
<h3>File Read: ${fileName}</h3>
|
|
1182
|
+
<div class="agent-subtitle">
|
|
1183
|
+
<span class="tool-type-badge">${fileCategory}</span>
|
|
1184
|
+
<span class="tool-id-badge">ID: ${toolId.slice(-8)}</span>
|
|
1185
|
+
</div>
|
|
1186
|
+
</div>
|
|
1187
|
+
</div>
|
|
1188
|
+
</div>
|
|
1189
|
+
<button class="agent-modal-close" id="agent-modal-close">×</button>
|
|
1190
|
+
</div>
|
|
1191
|
+
|
|
1192
|
+
<div class="agent-modal-content">
|
|
1193
|
+
<div class="raw-parameters-section primary-section">
|
|
1194
|
+
<h4>๐ง Tool Parameters</h4>
|
|
1195
|
+
<div class="raw-params-container">
|
|
1196
|
+
<pre class="raw-params-json">${JSON.stringify(input, null, 2)}</pre>
|
|
1197
|
+
</div>
|
|
1198
|
+
<div class="params-summary">
|
|
1199
|
+
<span class="param-chip">Tool ID: ${toolId.slice(-8)}</span>
|
|
1200
|
+
<span class="param-chip">File: ${fileName}</span>
|
|
1201
|
+
${offset ? `<span class="param-chip">From line: ${offset}</span>` : ''}
|
|
1202
|
+
${limit ? `<span class="param-chip">Lines: ${limit}</span>` : '<span class="param-chip">Complete file</span>'}
|
|
1203
|
+
</div>
|
|
1204
|
+
</div>
|
|
1205
|
+
|
|
1206
|
+
<div class="read-operation-section">
|
|
1207
|
+
<h4>๐ Read Operation Details</h4>
|
|
1208
|
+
<div class="operation-details">
|
|
1209
|
+
<div class="operation-item">
|
|
1210
|
+
<span class="operation-label">Full Path:</span>
|
|
1211
|
+
<code class="operation-value">${filePath}</code>
|
|
1212
|
+
</div>
|
|
1213
|
+
${offset ? `
|
|
1214
|
+
<div class="operation-item">
|
|
1215
|
+
<span class="operation-label">Starting Line:</span>
|
|
1216
|
+
<code class="operation-value">${offset}</code>
|
|
1217
|
+
</div>
|
|
1218
|
+
` : `
|
|
1219
|
+
<div class="operation-item">
|
|
1220
|
+
<span class="operation-label">Read Scope:</span>
|
|
1221
|
+
<code class="operation-value">From beginning</code>
|
|
1222
|
+
</div>
|
|
1223
|
+
`}
|
|
1224
|
+
${limit ? `
|
|
1225
|
+
<div class="operation-item">
|
|
1226
|
+
<span class="operation-label">Lines Read:</span>
|
|
1227
|
+
<code class="operation-value">${limit} lines</code>
|
|
1228
|
+
</div>
|
|
1229
|
+
` : `
|
|
1230
|
+
<div class="operation-item">
|
|
1231
|
+
<span class="operation-label">Read Scope:</span>
|
|
1232
|
+
<code class="operation-value">Complete file</code>
|
|
1233
|
+
</div>
|
|
1234
|
+
`}
|
|
1235
|
+
<div class="operation-item">
|
|
1236
|
+
<span class="operation-label">Tool ID:</span>
|
|
1237
|
+
<code class="operation-value">${toolId}</code>
|
|
1238
|
+
</div>
|
|
1239
|
+
</div>
|
|
1240
|
+
</div>
|
|
1241
|
+
|
|
1242
|
+
|
|
1243
|
+
<div class="file-insights-section">
|
|
1244
|
+
<h4>๐ File Insights</h4>
|
|
1245
|
+
<div class="insights-grid">
|
|
1246
|
+
<div class="insight-card">
|
|
1247
|
+
<div class="insight-header">
|
|
1248
|
+
<span class="insight-icon">${contextIcon}</span>
|
|
1249
|
+
<span class="insight-title">File Classification</span>
|
|
1250
|
+
</div>
|
|
1251
|
+
<div class="insight-content">${fileCategory}</div>
|
|
1252
|
+
</div>
|
|
1253
|
+
<div class="insight-card">
|
|
1254
|
+
<div class="insight-header">
|
|
1255
|
+
<span class="insight-icon">๐</span>
|
|
1256
|
+
<span class="insight-title">Location Context</span>
|
|
1257
|
+
</div>
|
|
1258
|
+
<div class="insight-content">${relativeDir}</div>
|
|
1259
|
+
</div>
|
|
1260
|
+
<div class="insight-card">
|
|
1261
|
+
<div class="insight-header">
|
|
1262
|
+
<span class="insight-icon">๐ฏ</span>
|
|
1263
|
+
<span class="insight-title">Read Strategy</span>
|
|
1264
|
+
</div>
|
|
1265
|
+
<div class="insight-content">${offset && limit ? `Partial read (${limit} lines from ${offset})` : limit ? `Limited read (${limit} lines)` : offset ? `From line ${offset}` : 'Complete file'}</div>
|
|
1266
|
+
</div>
|
|
1267
|
+
<div class="insight-card">
|
|
1268
|
+
<div class="insight-header">
|
|
1269
|
+
<span class="insight-icon">๐</span>
|
|
1270
|
+
<span class="insight-title">Project Impact</span>
|
|
1271
|
+
</div>
|
|
1272
|
+
<div class="insight-content">${filePurpose}</div>
|
|
1273
|
+
</div>
|
|
1274
|
+
</div>
|
|
1275
|
+
</div>
|
|
1276
|
+
|
|
1277
|
+
</div>
|
|
1278
|
+
</div>
|
|
1279
|
+
</div>
|
|
1280
|
+
`;
|
|
1281
|
+
|
|
1282
|
+
// Add modal to DOM
|
|
1283
|
+
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
|
1284
|
+
|
|
1285
|
+
// Bind close events
|
|
1286
|
+
document.getElementById('agent-modal-close').addEventListener('click', () => this.closeAgentModal());
|
|
1287
|
+
document.getElementById('agent-modal-overlay').addEventListener('click', (e) => {
|
|
1288
|
+
if (e.target.id === 'agent-modal-overlay') {
|
|
1289
|
+
this.closeAgentModal();
|
|
1290
|
+
}
|
|
1291
|
+
});
|
|
1292
|
+
|
|
1293
|
+
// ESC key to close - store reference for cleanup
|
|
1294
|
+
this.modalKeydownHandler = (e) => {
|
|
1295
|
+
if (e.key === 'Escape') {
|
|
1296
|
+
this.closeAgentModal();
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
document.addEventListener('keydown', this.modalKeydownHandler);
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
/**
|
|
1303
|
+
* Open Edit tool specific modal
|
|
1304
|
+
* @param {Object} editToolData - Edit tool data
|
|
1305
|
+
*/
|
|
1306
|
+
openEditToolModal(editToolData) {
|
|
1307
|
+
const input = editToolData.input || {};
|
|
1308
|
+
const filePath = input.file_path || 'Unknown file';
|
|
1309
|
+
const fileName = filePath.split('/').pop() || 'Unknown';
|
|
1310
|
+
const oldString = input.old_string || '';
|
|
1311
|
+
const newString = input.new_string || '';
|
|
1312
|
+
const replaceAll = input.replace_all || false;
|
|
1313
|
+
const toolId = editToolData.id || 'unknown';
|
|
1314
|
+
|
|
1315
|
+
const modalHTML = `
|
|
1316
|
+
<div class="agent-modal-overlay" id="agent-modal-overlay">
|
|
1317
|
+
<div class="agent-modal edit-tool-modal">
|
|
1318
|
+
<div class="agent-modal-header">
|
|
1319
|
+
<div class="agent-modal-title">
|
|
1320
|
+
<div class="agent-title-main">
|
|
1321
|
+
<div class="tool-icon edit-tool">
|
|
1322
|
+
<span style="font-size: 20px;">โ๏ธ</span>
|
|
1323
|
+
</div>
|
|
1324
|
+
<div class="agent-title-info">
|
|
1325
|
+
<h3>File Edit: ${fileName}</h3>
|
|
1326
|
+
<div class="agent-subtitle">
|
|
1327
|
+
<span class="tool-type-badge">File Modification</span>
|
|
1328
|
+
<span class="tool-id-badge">ID: ${toolId.slice(-8)}</span>
|
|
1329
|
+
</div>
|
|
1330
|
+
</div>
|
|
1331
|
+
</div>
|
|
1332
|
+
</div>
|
|
1333
|
+
<button class="agent-modal-close" id="agent-modal-close">×</button>
|
|
1334
|
+
</div>
|
|
1335
|
+
|
|
1336
|
+
<div class="agent-modal-content">
|
|
1337
|
+
<div class="raw-parameters-section primary-section">
|
|
1338
|
+
<h4>๐ง Tool Parameters</h4>
|
|
1339
|
+
<div class="raw-params-container">
|
|
1340
|
+
<pre class="raw-params-json">${JSON.stringify(input, null, 2)}</pre>
|
|
1341
|
+
</div>
|
|
1342
|
+
<div class="params-summary">
|
|
1343
|
+
<span class="param-chip">Tool ID: ${toolId.slice(-8)}</span>
|
|
1344
|
+
<span class="param-chip">File: ${fileName}</span>
|
|
1345
|
+
<span class="param-chip">Replace All: ${replaceAll ? 'Yes' : 'No'}</span>
|
|
1346
|
+
<span class="param-chip">Change Size: ${oldString.length} โ ${newString.length} chars</span>
|
|
1347
|
+
</div>
|
|
1348
|
+
</div>
|
|
1349
|
+
|
|
1350
|
+
<div class="edit-changes-section">
|
|
1351
|
+
<div class="diff-header-section">
|
|
1352
|
+
<h4>๐ Edit Diff</h4>
|
|
1353
|
+
<div class="diff-mode-toggle">
|
|
1354
|
+
<button class="diff-mode-btn active" data-mode="lines" onclick="window.switchDiffMode('lines', this)">
|
|
1355
|
+
<span>๐</span> By Lines
|
|
1356
|
+
</button>
|
|
1357
|
+
<button class="diff-mode-btn" data-mode="compact" onclick="window.switchDiffMode('compact', this)">
|
|
1358
|
+
<span>๐</span> Smart Diff
|
|
1359
|
+
</button>
|
|
1360
|
+
</div>
|
|
1361
|
+
</div>
|
|
1362
|
+
<div class="diff-container" id="diff-container">
|
|
1363
|
+
${this.generateDiffView(oldString, newString, 'lines')}
|
|
1364
|
+
</div>
|
|
1365
|
+
</div>
|
|
1366
|
+
|
|
1367
|
+
|
|
1368
|
+
<div class="file-insights-section">
|
|
1369
|
+
<h4>๐ Edit Insights</h4>
|
|
1370
|
+
<div class="insights-grid">
|
|
1371
|
+
<div class="insight-card">
|
|
1372
|
+
<div class="insight-header">
|
|
1373
|
+
<span class="insight-icon">๐</span>
|
|
1374
|
+
<span class="insight-title">Target File</span>
|
|
1375
|
+
</div>
|
|
1376
|
+
<div class="insight-content">${fileName}</div>
|
|
1377
|
+
</div>
|
|
1378
|
+
<div class="insight-card">
|
|
1379
|
+
<div class="insight-header">
|
|
1380
|
+
<span class="insight-icon">๐</span>
|
|
1381
|
+
<span class="insight-title">Content Change</span>
|
|
1382
|
+
</div>
|
|
1383
|
+
<div class="insight-content">${oldString.length > newString.length ? 'Reduced' : oldString.length < newString.length ? 'Expanded' : 'Same'} (${newString.length - oldString.length > 0 ? '+' : ''}${newString.length - oldString.length} chars)</div>
|
|
1384
|
+
</div>
|
|
1385
|
+
<div class="insight-card">
|
|
1386
|
+
<div class="insight-header">
|
|
1387
|
+
<span class="insight-icon">๐</span>
|
|
1388
|
+
<span class="insight-title">Replace Mode</span>
|
|
1389
|
+
</div>
|
|
1390
|
+
<div class="insight-content">${replaceAll ? 'All Occurrences' : 'First Occurrence'}</div>
|
|
1391
|
+
</div>
|
|
1392
|
+
<div class="insight-card">
|
|
1393
|
+
<div class="insight-header">
|
|
1394
|
+
<span class="insight-icon">๐ฏ</span>
|
|
1395
|
+
<span class="insight-title">Edit Type</span>
|
|
1396
|
+
</div>
|
|
1397
|
+
<div class="insight-content">${oldString.length === 0 ? 'Addition' : newString.length === 0 ? 'Deletion' : 'Modification'}</div>
|
|
1398
|
+
</div>
|
|
1399
|
+
</div>
|
|
1400
|
+
</div>
|
|
1401
|
+
</div>
|
|
1402
|
+
</div>
|
|
1403
|
+
</div>
|
|
1404
|
+
`;
|
|
1405
|
+
|
|
1406
|
+
// Add modal to DOM
|
|
1407
|
+
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
|
1408
|
+
|
|
1409
|
+
// Bind close events
|
|
1410
|
+
document.getElementById('agent-modal-close').addEventListener('click', () => this.closeAgentModal());
|
|
1411
|
+
document.getElementById('agent-modal-overlay').addEventListener('click', (e) => {
|
|
1412
|
+
if (e.target.id === 'agent-modal-overlay') {
|
|
1413
|
+
this.closeAgentModal();
|
|
1414
|
+
}
|
|
1415
|
+
});
|
|
1416
|
+
|
|
1417
|
+
// ESC key to close
|
|
1418
|
+
this.modalKeydownHandler = (e) => {
|
|
1419
|
+
if (e.key === 'Escape') {
|
|
1420
|
+
this.closeAgentModal();
|
|
1421
|
+
}
|
|
1422
|
+
};
|
|
1423
|
+
document.addEventListener('keydown', this.modalKeydownHandler);
|
|
1424
|
+
|
|
1425
|
+
// Store the tool data for mode switching
|
|
1426
|
+
window.currentEditData = { oldString, newString };
|
|
1427
|
+
|
|
1428
|
+
// Add global function for diff mode switching
|
|
1429
|
+
const self = this;
|
|
1430
|
+
window.switchDiffMode = function(mode, button) {
|
|
1431
|
+
// Update button states
|
|
1432
|
+
document.querySelectorAll('.diff-mode-btn').forEach(btn => btn.classList.remove('active'));
|
|
1433
|
+
button.classList.add('active');
|
|
1434
|
+
|
|
1435
|
+
// Update diff content
|
|
1436
|
+
const container = document.getElementById('diff-container');
|
|
1437
|
+
if (container && window.currentEditData) {
|
|
1438
|
+
container.innerHTML = self.generateDiffView(
|
|
1439
|
+
window.currentEditData.oldString,
|
|
1440
|
+
window.currentEditData.newString,
|
|
1441
|
+
mode
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
/**
|
|
1448
|
+
* Generate diff view for edit changes
|
|
1449
|
+
* @param {string} oldText - Original text
|
|
1450
|
+
* @param {string} newText - New text
|
|
1451
|
+
* @param {string} mode - 'lines' or 'compact'
|
|
1452
|
+
* @returns {string} HTML diff view
|
|
1453
|
+
*/
|
|
1454
|
+
generateDiffView(oldText, newText, mode = 'lines') {
|
|
1455
|
+
if (mode === 'compact') {
|
|
1456
|
+
return this.generateSmartDiff(oldText, newText);
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
// Split text into lines for better diff visualization
|
|
1460
|
+
const oldLines = oldText.split('\n');
|
|
1461
|
+
const newLines = newText.split('\n');
|
|
1462
|
+
|
|
1463
|
+
// Limit lines for display (show first 20 lines max)
|
|
1464
|
+
const maxLines = 20;
|
|
1465
|
+
const oldDisplay = oldLines.slice(0, maxLines);
|
|
1466
|
+
const newDisplay = newLines.slice(0, maxLines);
|
|
1467
|
+
const oldTruncated = oldLines.length > maxLines;
|
|
1468
|
+
const newTruncated = newLines.length > maxLines;
|
|
1469
|
+
|
|
1470
|
+
let diffHtml = '<div class="diff-editor">';
|
|
1471
|
+
|
|
1472
|
+
// Header with file info
|
|
1473
|
+
diffHtml += `
|
|
1474
|
+
<div class="diff-header">
|
|
1475
|
+
<span class="diff-stats">-${oldLines.length} lines, +${newLines.length} lines</span>
|
|
1476
|
+
</div>
|
|
1477
|
+
`;
|
|
1478
|
+
|
|
1479
|
+
// Old text (removed lines)
|
|
1480
|
+
if (oldDisplay.length > 0) {
|
|
1481
|
+
diffHtml += '<div class="diff-section removed">';
|
|
1482
|
+
oldDisplay.forEach((line) => {
|
|
1483
|
+
diffHtml += `
|
|
1484
|
+
<div class="diff-line removed-line">
|
|
1485
|
+
<span class="line-prefix">-</span>
|
|
1486
|
+
<span class="line-content">${this.escapeHtml(line) || ' '}</span>
|
|
1487
|
+
</div>
|
|
1488
|
+
`;
|
|
1489
|
+
});
|
|
1490
|
+
if (oldTruncated) {
|
|
1491
|
+
diffHtml += `
|
|
1492
|
+
<div class="diff-line truncated">
|
|
1493
|
+
<span class="line-prefix"></span>
|
|
1494
|
+
<span class="line-content">... ${oldLines.length - maxLines} more lines ...</span>
|
|
1495
|
+
</div>
|
|
1496
|
+
`;
|
|
1497
|
+
}
|
|
1498
|
+
diffHtml += '</div>';
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
// New text (added lines)
|
|
1502
|
+
if (newDisplay.length > 0) {
|
|
1503
|
+
diffHtml += '<div class="diff-section added">';
|
|
1504
|
+
newDisplay.forEach((line) => {
|
|
1505
|
+
diffHtml += `
|
|
1506
|
+
<div class="diff-line added-line">
|
|
1507
|
+
<span class="line-prefix">+</span>
|
|
1508
|
+
<span class="line-content">${this.escapeHtml(line) || ' '}</span>
|
|
1509
|
+
</div>
|
|
1510
|
+
`;
|
|
1511
|
+
});
|
|
1512
|
+
if (newTruncated) {
|
|
1513
|
+
diffHtml += `
|
|
1514
|
+
<div class="diff-line truncated">
|
|
1515
|
+
<span class="line-prefix"></span>
|
|
1516
|
+
<span class="line-content">... ${newLines.length - maxLines} more lines ...</span>
|
|
1517
|
+
</div>
|
|
1518
|
+
`;
|
|
1519
|
+
}
|
|
1520
|
+
diffHtml += '</div>';
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
diffHtml += '</div>';
|
|
1524
|
+
|
|
1525
|
+
return diffHtml;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
/**
|
|
1529
|
+
* Generate smart diff that shows only changed sections
|
|
1530
|
+
* @param {string} oldText - Original text
|
|
1531
|
+
* @param {string} newText - New text
|
|
1532
|
+
* @returns {string} HTML smart diff view
|
|
1533
|
+
*/
|
|
1534
|
+
generateSmartDiff(oldText, newText) {
|
|
1535
|
+
const oldLines = oldText.split('\n');
|
|
1536
|
+
const newLines = newText.split('\n');
|
|
1537
|
+
|
|
1538
|
+
// Find differences using simple line-by-line comparison
|
|
1539
|
+
const changes = this.findLineChanges(oldLines, newLines);
|
|
1540
|
+
|
|
1541
|
+
let diffHtml = '<div class="diff-editor smart-diff">';
|
|
1542
|
+
|
|
1543
|
+
// Header with file info
|
|
1544
|
+
diffHtml += `
|
|
1545
|
+
<div class="diff-header">
|
|
1546
|
+
<span class="diff-stats">Smart diff showing ${changes.length} change block${changes.length !== 1 ? 's' : ''}</span>
|
|
1547
|
+
</div>
|
|
1548
|
+
`;
|
|
1549
|
+
|
|
1550
|
+
if (changes.length === 0) {
|
|
1551
|
+
diffHtml += `
|
|
1552
|
+
<div class="no-changes">
|
|
1553
|
+
<span class="no-changes-text">No line-level changes detected (whitespace or formatting only)</span>
|
|
1554
|
+
</div>
|
|
1555
|
+
`;
|
|
1556
|
+
} else {
|
|
1557
|
+
changes.forEach((change, index) => {
|
|
1558
|
+
diffHtml += `<div class="change-block" data-change-index="${index}">`;
|
|
1559
|
+
|
|
1560
|
+
// Show removed lines
|
|
1561
|
+
if (change.removed.length > 0) {
|
|
1562
|
+
change.removed.forEach(line => {
|
|
1563
|
+
diffHtml += `
|
|
1564
|
+
<div class="diff-line removed-line smart">
|
|
1565
|
+
<span class="line-prefix">-</span>
|
|
1566
|
+
<span class="line-content">${this.escapeHtml(line) || ' '}</span>
|
|
1567
|
+
</div>
|
|
1568
|
+
`;
|
|
1569
|
+
});
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
// Show added lines
|
|
1573
|
+
if (change.added.length > 0) {
|
|
1574
|
+
change.added.forEach(line => {
|
|
1575
|
+
diffHtml += `
|
|
1576
|
+
<div class="diff-line added-line smart">
|
|
1577
|
+
<span class="line-prefix">+</span>
|
|
1578
|
+
<span class="line-content">${this.escapeHtml(line) || ' '}</span>
|
|
1579
|
+
</div>
|
|
1580
|
+
`;
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
diffHtml += '</div>';
|
|
1585
|
+
|
|
1586
|
+
// Add separator between change blocks (except for the last one)
|
|
1587
|
+
if (index < changes.length - 1) {
|
|
1588
|
+
diffHtml += '<div class="change-separator">โฏ</div>';
|
|
1589
|
+
}
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
diffHtml += '</div>';
|
|
1594
|
+
|
|
1595
|
+
return diffHtml;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
/**
|
|
1599
|
+
* Find line changes between old and new text
|
|
1600
|
+
* @param {string[]} oldLines - Original lines
|
|
1601
|
+
* @param {string[]} newLines - New lines
|
|
1602
|
+
* @returns {Array} Array of change blocks
|
|
1603
|
+
*/
|
|
1604
|
+
findLineChanges(oldLines, newLines) {
|
|
1605
|
+
const changes = [];
|
|
1606
|
+
|
|
1607
|
+
// Simple approach: if texts are different, show both
|
|
1608
|
+
// This can be enhanced with more sophisticated diff algorithms
|
|
1609
|
+
if (oldLines.join('\n') !== newLines.join('\n')) {
|
|
1610
|
+
// For now, treat as one big change block
|
|
1611
|
+
// This could be enhanced to find actual line-by-line differences
|
|
1612
|
+
changes.push({
|
|
1613
|
+
removed: oldLines.slice(0, 10), // Limit to first 10 lines
|
|
1614
|
+
added: newLines.slice(0, 10) // Limit to first 10 lines
|
|
1615
|
+
});
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
return changes;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
/**
|
|
1622
|
+
* Open Write tool specific modal
|
|
1623
|
+
* @param {Object} writeToolData - Write tool data
|
|
1624
|
+
*/
|
|
1625
|
+
openWriteToolModal(writeToolData) {
|
|
1626
|
+
const input = writeToolData.input || {};
|
|
1627
|
+
const filePath = input.file_path || 'Unknown file';
|
|
1628
|
+
const fileName = filePath.split('/').pop() || 'Unknown';
|
|
1629
|
+
const fileExtension = fileName.includes('.') ? fileName.split('.').pop().toLowerCase() : 'txt';
|
|
1630
|
+
const content = input.content || '';
|
|
1631
|
+
const toolId = writeToolData.id || 'unknown';
|
|
1632
|
+
|
|
1633
|
+
// Analyze file context
|
|
1634
|
+
const isConfigFile = ['json', 'yml', 'yaml', 'toml', 'ini', 'conf', 'config'].includes(fileExtension);
|
|
1635
|
+
const isDocFile = ['md', 'txt', 'rst', 'adoc'].includes(fileExtension);
|
|
1636
|
+
const isCodeFile = ['js', 'ts', 'py', 'go', 'rs', 'java', 'cpp', 'c', 'h', 'css', 'html', 'jsx', 'tsx'].includes(fileExtension);
|
|
1637
|
+
const isProjectRoot = fileName.toLowerCase().includes('claude') || fileName.toLowerCase().includes('readme') || fileName.toLowerCase().includes('package');
|
|
1638
|
+
|
|
1639
|
+
let fileCategory = '';
|
|
1640
|
+
let contextIcon = '๐';
|
|
1641
|
+
|
|
1642
|
+
if (isProjectRoot) {
|
|
1643
|
+
fileCategory = 'Project Documentation';
|
|
1644
|
+
contextIcon = '๐';
|
|
1645
|
+
} else if (fileName.toLowerCase().includes('claude')) {
|
|
1646
|
+
fileCategory = 'Claude Configuration';
|
|
1647
|
+
contextIcon = '๐ค';
|
|
1648
|
+
} else if (isConfigFile) {
|
|
1649
|
+
fileCategory = 'Configuration File';
|
|
1650
|
+
contextIcon = 'โ๏ธ';
|
|
1651
|
+
} else if (isDocFile) {
|
|
1652
|
+
fileCategory = 'Documentation';
|
|
1653
|
+
contextIcon = '๐';
|
|
1654
|
+
} else if (isCodeFile) {
|
|
1655
|
+
fileCategory = 'Source Code';
|
|
1656
|
+
contextIcon = '๐ป';
|
|
1657
|
+
} else {
|
|
1658
|
+
fileCategory = 'Project File';
|
|
1659
|
+
contextIcon = '๐';
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
const modalHTML = `
|
|
1663
|
+
<div class="agent-modal-overlay" id="agent-modal-overlay">
|
|
1664
|
+
<div class="agent-modal write-tool-modal">
|
|
1665
|
+
<div class="agent-modal-header">
|
|
1666
|
+
<div class="agent-modal-title">
|
|
1667
|
+
<div class="agent-title-main">
|
|
1668
|
+
<div class="tool-icon write-tool">
|
|
1669
|
+
<span style="font-size: 20px;">${contextIcon}</span>
|
|
1670
|
+
</div>
|
|
1671
|
+
<div class="agent-title-info">
|
|
1672
|
+
<h3>File Creation: ${fileName}</h3>
|
|
1673
|
+
<div class="agent-subtitle">
|
|
1674
|
+
<span class="tool-type-badge">${fileCategory}</span>
|
|
1675
|
+
<span class="tool-id-badge">ID: ${toolId.slice(-8)}</span>
|
|
1676
|
+
</div>
|
|
1677
|
+
</div>
|
|
1678
|
+
</div>
|
|
1679
|
+
</div>
|
|
1680
|
+
<button class="agent-modal-close" id="agent-modal-close">×</button>
|
|
1681
|
+
</div>
|
|
1682
|
+
|
|
1683
|
+
<div class="agent-modal-content">
|
|
1684
|
+
<div class="raw-parameters-section primary-section">
|
|
1685
|
+
<h4>๐ง Tool Parameters</h4>
|
|
1686
|
+
<div class="raw-params-container">
|
|
1687
|
+
<pre class="raw-params-json">${JSON.stringify(input, null, 2)}</pre>
|
|
1688
|
+
</div>
|
|
1689
|
+
<div class="params-summary">
|
|
1690
|
+
<span class="param-chip">Tool ID: ${toolId.slice(-8)}</span>
|
|
1691
|
+
<span class="param-chip">File: ${fileName}</span>
|
|
1692
|
+
<span class="param-chip">Size: ${content.length} characters</span>
|
|
1693
|
+
<span class="param-chip">Type: ${fileExtension.toUpperCase()}</span>
|
|
1694
|
+
</div>
|
|
1695
|
+
</div>
|
|
1696
|
+
|
|
1697
|
+
<div class="file-content-section">
|
|
1698
|
+
<div class="content-header-section">
|
|
1699
|
+
<h4>๐ File Content Preview</h4>
|
|
1700
|
+
<div class="content-stats">
|
|
1701
|
+
<span class="content-stat">${content.split('\\n').length} lines</span>
|
|
1702
|
+
<span class="content-stat">${content.length} chars</span>
|
|
1703
|
+
<span class="content-stat">${Math.round(content.length / 1024 * 100) / 100} KB</span>
|
|
1704
|
+
</div>
|
|
1705
|
+
</div>
|
|
1706
|
+
<div class="content-preview-container">
|
|
1707
|
+
<pre class="content-preview">${this.escapeHtml(content.substring(0, 1000))}${content.length > 1000 ? '\\n\\n... [truncated, showing first 1000 characters]' : ''}</pre>
|
|
1708
|
+
</div>
|
|
1709
|
+
</div>
|
|
1710
|
+
|
|
1711
|
+
|
|
1712
|
+
<div class="file-insights-section">
|
|
1713
|
+
<h4>๐ Creation Insights</h4>
|
|
1714
|
+
<div class="insights-grid">
|
|
1715
|
+
<div class="insight-card">
|
|
1716
|
+
<div class="insight-header">
|
|
1717
|
+
<span class="insight-icon">${contextIcon}</span>
|
|
1718
|
+
<span class="insight-title">File Type</span>
|
|
1719
|
+
</div>
|
|
1720
|
+
<div class="insight-content">${fileCategory}</div>
|
|
1721
|
+
</div>
|
|
1722
|
+
<div class="insight-card">
|
|
1723
|
+
<div class="insight-header">
|
|
1724
|
+
<span class="insight-icon">๐</span>
|
|
1725
|
+
<span class="insight-title">Content Size</span>
|
|
1726
|
+
</div>
|
|
1727
|
+
<div class="insight-content">${content.length} characters</div>
|
|
1728
|
+
</div>
|
|
1729
|
+
<div class="insight-card">
|
|
1730
|
+
<div class="insight-header">
|
|
1731
|
+
<span class="insight-icon">๐</span>
|
|
1732
|
+
<span class="insight-title">Structure</span>
|
|
1733
|
+
</div>
|
|
1734
|
+
<div class="insight-content">${content.split('\\n').length} lines</div>
|
|
1735
|
+
</div>
|
|
1736
|
+
<div class="insight-card">
|
|
1737
|
+
<div class="insight-header">
|
|
1738
|
+
<span class="insight-icon">๐</span>
|
|
1739
|
+
<span class="insight-title">Purpose</span>
|
|
1740
|
+
</div>
|
|
1741
|
+
<div class="insight-content">${content.length === 0 ? 'Empty file' : content.length < 100 ? 'Simple file' : content.length < 1000 ? 'Medium file' : 'Large file'}</div>
|
|
1742
|
+
</div>
|
|
1743
|
+
</div>
|
|
1744
|
+
</div>
|
|
1745
|
+
</div>
|
|
1746
|
+
</div>
|
|
1747
|
+
</div>
|
|
1748
|
+
`;
|
|
1749
|
+
|
|
1750
|
+
// Add modal to DOM
|
|
1751
|
+
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
|
1752
|
+
|
|
1753
|
+
// Bind close events
|
|
1754
|
+
document.getElementById('agent-modal-close').addEventListener('click', () => this.closeAgentModal());
|
|
1755
|
+
document.getElementById('agent-modal-overlay').addEventListener('click', (e) => {
|
|
1756
|
+
if (e.target.id === 'agent-modal-overlay') {
|
|
1757
|
+
this.closeAgentModal();
|
|
1758
|
+
}
|
|
1759
|
+
});
|
|
1760
|
+
|
|
1761
|
+
// ESC key to close
|
|
1762
|
+
this.modalKeydownHandler = (e) => {
|
|
1763
|
+
if (e.key === 'Escape') {
|
|
1764
|
+
this.closeAgentModal();
|
|
1765
|
+
}
|
|
1766
|
+
};
|
|
1767
|
+
document.addEventListener('keydown', this.modalKeydownHandler);
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
/**
|
|
1771
|
+
* Open Glob tool specific modal
|
|
1772
|
+
* @param {Object} globToolData - Glob tool data
|
|
1773
|
+
*/
|
|
1774
|
+
openGlobToolModal(globToolData) {
|
|
1775
|
+
console.log('๐ Opening Glob tool modal with data:', globToolData);
|
|
1776
|
+
|
|
1777
|
+
if (!globToolData || !globToolData.input) {
|
|
1778
|
+
console.warn('โ ๏ธ Glob tool data missing or invalid');
|
|
1779
|
+
return;
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
// Extract Glob parameters
|
|
1783
|
+
const pattern = globToolData.input.pattern || '';
|
|
1784
|
+
const searchPath = globToolData.input.path || '(current directory)';
|
|
1785
|
+
|
|
1786
|
+
// Categorize glob pattern
|
|
1787
|
+
let patternType = 'General';
|
|
1788
|
+
let patternDescription = 'File pattern search';
|
|
1789
|
+
|
|
1790
|
+
if (pattern.includes('**')) {
|
|
1791
|
+
patternType = 'Recursive';
|
|
1792
|
+
patternDescription = 'Deep directory search with recursive matching';
|
|
1793
|
+
} else if (pattern.includes('*')) {
|
|
1794
|
+
patternType = 'Wildcard';
|
|
1795
|
+
patternDescription = 'Basic wildcard pattern matching';
|
|
1796
|
+
} else if (pattern.includes('.')) {
|
|
1797
|
+
patternType = 'Extension';
|
|
1798
|
+
patternDescription = 'File extension filtering';
|
|
1799
|
+
} else if (pattern.includes('/')) {
|
|
1800
|
+
patternType = 'Path-based';
|
|
1801
|
+
patternDescription = 'Directory structure pattern';
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
// Detect pattern specifics
|
|
1805
|
+
const isJavaScript = pattern.includes('.js') || pattern.includes('.ts') || pattern.includes('.jsx') || pattern.includes('.tsx');
|
|
1806
|
+
const isStyles = pattern.includes('.css') || pattern.includes('.scss') || pattern.includes('.sass');
|
|
1807
|
+
const isMarkdown = pattern.includes('.md') || pattern.includes('.mdx');
|
|
1808
|
+
const isConfig = pattern.includes('config') || pattern.includes('.json') || pattern.includes('.yml') || pattern.includes('.yaml');
|
|
1809
|
+
|
|
1810
|
+
let projectContext = '';
|
|
1811
|
+
if (isJavaScript) projectContext = 'JavaScript/TypeScript files';
|
|
1812
|
+
else if (isStyles) projectContext = 'Stylesheet files';
|
|
1813
|
+
else if (isMarkdown) projectContext = 'Documentation files';
|
|
1814
|
+
else if (isConfig) projectContext = 'Configuration files';
|
|
1815
|
+
else projectContext = 'General file search';
|
|
1816
|
+
|
|
1817
|
+
const modalContent = `
|
|
1818
|
+
<div class="agent-modal-header">
|
|
1819
|
+
<div class="agent-modal-title">
|
|
1820
|
+
<div class="agent-title-main">
|
|
1821
|
+
<div class="tool-icon glob-tool">
|
|
1822
|
+
<span style="font-size: 20px;">๐</span>
|
|
1823
|
+
</div>
|
|
1824
|
+
<div class="agent-title-info">
|
|
1825
|
+
<h3>Glob Pattern: ${this.escapeHtml(pattern)}</h3>
|
|
1826
|
+
<div class="agent-subtitle">
|
|
1827
|
+
<span class="tool-type-badge">${patternType}</span>
|
|
1828
|
+
<span class="tool-id-badge">ID: ${globToolData.id ? globToolData.id.slice(-8) : 'unknown'}</span>
|
|
1829
|
+
</div>
|
|
1830
|
+
</div>
|
|
1831
|
+
</div>
|
|
1832
|
+
</div>
|
|
1833
|
+
<button class="agent-modal-close" id="agent-modal-close">×</button>
|
|
1834
|
+
</div>
|
|
1835
|
+
|
|
1836
|
+
<div class="agent-modal-content">
|
|
1837
|
+
<!-- Primary Section: Tool Parameters -->
|
|
1838
|
+
<div class="raw-parameters-section primary-section">
|
|
1839
|
+
<h4>๐ง Tool Parameters</h4>
|
|
1840
|
+
<div class="raw-params-container">
|
|
1841
|
+
<pre class="raw-params-json">${this.escapeHtml(JSON.stringify(globToolData.input, null, 2))}</pre>
|
|
1842
|
+
</div>
|
|
1843
|
+
</div>
|
|
1844
|
+
|
|
1845
|
+
<!-- Pattern Analysis -->
|
|
1846
|
+
<div class="glob-pattern-section">
|
|
1847
|
+
<h4>๐ฏ Pattern Analysis</h4>
|
|
1848
|
+
<div class="tool-details-grid">
|
|
1849
|
+
<div class="tool-detail-item">
|
|
1850
|
+
<span class="tool-detail-label">Search Pattern:</span>
|
|
1851
|
+
<span class="tool-detail-value pattern-display">${this.escapeHtml(pattern)}</span>
|
|
1852
|
+
</div>
|
|
1853
|
+
<div class="tool-detail-item">
|
|
1854
|
+
<span class="tool-detail-label">Pattern Type:</span>
|
|
1855
|
+
<span class="tool-detail-value">${patternType}</span>
|
|
1856
|
+
</div>
|
|
1857
|
+
<div class="tool-detail-item">
|
|
1858
|
+
<span class="tool-detail-label">Description:</span>
|
|
1859
|
+
<span class="tool-detail-value">${patternDescription}</span>
|
|
1860
|
+
</div>
|
|
1861
|
+
<div class="tool-detail-item">
|
|
1862
|
+
<span class="tool-detail-label">Search Location:</span>
|
|
1863
|
+
<span class="tool-detail-value">${this.escapeHtml(searchPath)}</span>
|
|
1864
|
+
</div>
|
|
1865
|
+
<div class="tool-detail-item">
|
|
1866
|
+
<span class="tool-detail-label">Target Files:</span>
|
|
1867
|
+
<span class="tool-detail-value">${projectContext}</span>
|
|
1868
|
+
</div>
|
|
1869
|
+
</div>
|
|
1870
|
+
</div>
|
|
1871
|
+
|
|
1872
|
+
<!-- Pattern Breakdown -->
|
|
1873
|
+
<div class="glob-components-section">
|
|
1874
|
+
<h4>๐งฉ Pattern Components</h4>
|
|
1875
|
+
<div class="pattern-breakdown">
|
|
1876
|
+
${this.analyzeGlobPattern(pattern)}
|
|
1877
|
+
</div>
|
|
1878
|
+
</div>
|
|
1879
|
+
|
|
1880
|
+
<!-- File Discovery Insights -->
|
|
1881
|
+
<div class="glob-insights-section">
|
|
1882
|
+
<h4>๐ Discovery Insights</h4>
|
|
1883
|
+
<div class="tool-insights">
|
|
1884
|
+
<div class="insight-item">
|
|
1885
|
+
<span class="insight-label">Scope:</span>
|
|
1886
|
+
<span class="insight-value">${pattern.includes('**') ? 'Recursive (all subdirectories)' : 'Current level only'}</span>
|
|
1887
|
+
</div>
|
|
1888
|
+
<div class="insight-item">
|
|
1889
|
+
<span class="insight-label">Efficiency:</span>
|
|
1890
|
+
<span class="insight-value">${pattern.length > 20 ? 'Complex pattern (slower)' : 'Simple pattern (fast)'}</span>
|
|
1891
|
+
</div>
|
|
1892
|
+
<div class="insight-item">
|
|
1893
|
+
<span class="insight-label">Match Strategy:</span>
|
|
1894
|
+
<span class="insight-value">${pattern.includes('*') ? 'Wildcard matching' : 'Exact pattern'}</span>
|
|
1895
|
+
</div>
|
|
1896
|
+
<div class="insight-item">
|
|
1897
|
+
<span class="insight-label">File Context:</span>
|
|
1898
|
+
<span class="insight-value">${projectContext}</span>
|
|
1899
|
+
</div>
|
|
1900
|
+
</div>
|
|
1901
|
+
</div>
|
|
1902
|
+
</div>
|
|
1903
|
+
`;
|
|
1904
|
+
|
|
1905
|
+
const modalHTML = `
|
|
1906
|
+
<div class="agent-modal-overlay" id="agent-modal-overlay">
|
|
1907
|
+
<div class="agent-modal glob-tool-modal">
|
|
1908
|
+
${modalContent}
|
|
1909
|
+
</div>
|
|
1910
|
+
</div>
|
|
1911
|
+
`;
|
|
1912
|
+
|
|
1913
|
+
// Add modal to DOM
|
|
1914
|
+
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
|
1915
|
+
|
|
1916
|
+
// Bind close events
|
|
1917
|
+
document.getElementById('agent-modal-close').addEventListener('click', () => this.closeAgentModal());
|
|
1918
|
+
document.getElementById('agent-modal-overlay').addEventListener('click', (e) => {
|
|
1919
|
+
if (e.target.id === 'agent-modal-overlay') {
|
|
1920
|
+
this.closeAgentModal();
|
|
1921
|
+
}
|
|
1922
|
+
});
|
|
1923
|
+
|
|
1924
|
+
// ESC key to close - store reference for cleanup
|
|
1925
|
+
this.modalKeydownHandler = (e) => {
|
|
1926
|
+
if (e.key === 'Escape') {
|
|
1927
|
+
this.closeAgentModal();
|
|
1928
|
+
}
|
|
1929
|
+
};
|
|
1930
|
+
document.addEventListener('keydown', this.modalKeydownHandler);
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
/**
|
|
1934
|
+
* Analyze glob pattern components
|
|
1935
|
+
* @param {string} pattern - Glob pattern to analyze
|
|
1936
|
+
* @returns {string} HTML breakdown of pattern
|
|
1937
|
+
*/
|
|
1938
|
+
analyzeGlobPattern(pattern) {
|
|
1939
|
+
if (!pattern) return '<span class="pattern-empty">No pattern specified</span>';
|
|
1940
|
+
|
|
1941
|
+
const components = [];
|
|
1942
|
+
const parts = pattern.split('/');
|
|
1943
|
+
|
|
1944
|
+
parts.forEach((part, index) => {
|
|
1945
|
+
let description = '';
|
|
1946
|
+
let type = 'literal';
|
|
1947
|
+
|
|
1948
|
+
if (part === '**') {
|
|
1949
|
+
description = 'Recursive directory wildcard (matches any nested path)';
|
|
1950
|
+
type = 'recursive';
|
|
1951
|
+
} else if (part.includes('*')) {
|
|
1952
|
+
if (part === '*') {
|
|
1953
|
+
description = 'Match any single directory or filename';
|
|
1954
|
+
type = 'wildcard';
|
|
1955
|
+
} else if (part.startsWith('*.')) {
|
|
1956
|
+
description = `Match files with ${part.substring(2)} extension`;
|
|
1957
|
+
type = 'extension';
|
|
1958
|
+
} else {
|
|
1959
|
+
description = 'Partial wildcard match';
|
|
1960
|
+
type = 'partial-wildcard';
|
|
1961
|
+
}
|
|
1962
|
+
} else if (part.includes('?')) {
|
|
1963
|
+
description = 'Single character wildcard';
|
|
1964
|
+
type = 'char-wildcard';
|
|
1965
|
+
} else if (part.includes('[') && part.includes(']')) {
|
|
1966
|
+
description = 'Character class match';
|
|
1967
|
+
type = 'char-class';
|
|
1968
|
+
} else if (part) {
|
|
1969
|
+
description = 'Literal directory/filename';
|
|
1970
|
+
type = 'literal';
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
if (part) {
|
|
1974
|
+
components.push(`
|
|
1975
|
+
<div class="pattern-component ${type}">
|
|
1976
|
+
<code class="pattern-part">${this.escapeHtml(part)}</code>
|
|
1977
|
+
<span class="pattern-desc">${description}</span>
|
|
1978
|
+
</div>
|
|
1979
|
+
`);
|
|
1980
|
+
}
|
|
1981
|
+
});
|
|
1982
|
+
|
|
1983
|
+
return components.length > 0 ? components.join('') : '<span class="pattern-empty">Empty pattern</span>';
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
/**
|
|
1987
|
+
* Open Grep tool specific modal
|
|
1988
|
+
* @param {Object} grepToolData - Grep tool data
|
|
1989
|
+
*/
|
|
1990
|
+
openGrepToolModal(grepToolData) {
|
|
1991
|
+
console.log('๐ Opening Grep tool modal with data:', grepToolData);
|
|
1992
|
+
|
|
1993
|
+
if (!grepToolData || !grepToolData.input) {
|
|
1994
|
+
console.warn('โ ๏ธ Grep tool data missing or invalid');
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
// Extract Grep parameters
|
|
1999
|
+
const pattern = grepToolData.input.pattern || '';
|
|
2000
|
+
const searchPath = grepToolData.input.path || '(current directory)';
|
|
2001
|
+
const outputMode = grepToolData.input.output_mode || 'files_with_matches';
|
|
2002
|
+
const glob = grepToolData.input.glob || '';
|
|
2003
|
+
const type = grepToolData.input.type || '';
|
|
2004
|
+
const caseInsensitive = grepToolData.input['-i'] || false;
|
|
2005
|
+
const contextBefore = grepToolData.input['-B'] || 0;
|
|
2006
|
+
const contextAfter = grepToolData.input['-A'] || 0;
|
|
2007
|
+
const contextAround = grepToolData.input['-C'] || 0;
|
|
2008
|
+
const showLineNumbers = grepToolData.input['-n'] || false;
|
|
2009
|
+
const multiline = grepToolData.input.multiline || false;
|
|
2010
|
+
|
|
2011
|
+
// Analyze search pattern
|
|
2012
|
+
let patternType = 'Literal';
|
|
2013
|
+
let patternComplexity = 'Simple';
|
|
2014
|
+
let searchScope = 'Text content';
|
|
2015
|
+
|
|
2016
|
+
// Detect regex patterns
|
|
2017
|
+
if (pattern.includes('.*') || pattern.includes('.+') || pattern.includes('\\w') || pattern.includes('\\d')) {
|
|
2018
|
+
patternType = 'Regular Expression';
|
|
2019
|
+
patternComplexity = 'Complex';
|
|
2020
|
+
} else if (pattern.includes('*') || pattern.includes('?') || pattern.includes('[') || pattern.includes(']')) {
|
|
2021
|
+
patternType = 'Wildcard Pattern';
|
|
2022
|
+
patternComplexity = 'Moderate';
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
// Detect specific search types
|
|
2026
|
+
if (pattern.includes('function') || pattern.includes('class') || pattern.includes('import')) {
|
|
2027
|
+
searchScope = 'Code structure';
|
|
2028
|
+
} else if (pattern.includes('TODO') || pattern.includes('FIXME') || pattern.includes('NOTE')) {
|
|
2029
|
+
searchScope = 'Code comments';
|
|
2030
|
+
} else if (pattern.includes('error') || pattern.includes('Error') || pattern.includes('exception')) {
|
|
2031
|
+
searchScope = 'Error handling';
|
|
2032
|
+
} else if (pattern.includes('test') || pattern.includes('spec') || pattern.includes('describe')) {
|
|
2033
|
+
searchScope = 'Test code';
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
// File type analysis
|
|
2037
|
+
let fileContext = 'All files';
|
|
2038
|
+
if (type) {
|
|
2039
|
+
switch (type) {
|
|
2040
|
+
case 'js': fileContext = 'JavaScript files'; break;
|
|
2041
|
+
case 'ts': fileContext = 'TypeScript files'; break;
|
|
2042
|
+
case 'py': fileContext = 'Python files'; break;
|
|
2043
|
+
case 'go': fileContext = 'Go files'; break;
|
|
2044
|
+
case 'rust': fileContext = 'Rust files'; break;
|
|
2045
|
+
case 'java': fileContext = 'Java files'; break;
|
|
2046
|
+
case 'css': fileContext = 'CSS files'; break;
|
|
2047
|
+
case 'html': fileContext = 'HTML files'; break;
|
|
2048
|
+
case 'md': fileContext = 'Markdown files'; break;
|
|
2049
|
+
default: fileContext = `${type} files`; break;
|
|
2050
|
+
}
|
|
2051
|
+
} else if (glob) {
|
|
2052
|
+
if (glob.includes('*.js') || glob.includes('*.ts')) fileContext = 'JavaScript/TypeScript files';
|
|
2053
|
+
else if (glob.includes('*.py')) fileContext = 'Python files';
|
|
2054
|
+
else if (glob.includes('*.md')) fileContext = 'Documentation files';
|
|
2055
|
+
else if (glob.includes('*.css') || glob.includes('*.scss')) fileContext = 'Stylesheet files';
|
|
2056
|
+
else fileContext = `Files matching "${glob}"`;
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
const modalContent = `
|
|
2060
|
+
<div class="agent-modal-header">
|
|
2061
|
+
<div class="agent-modal-title">
|
|
2062
|
+
<div class="agent-title-main">
|
|
2063
|
+
<div class="tool-icon grep-tool">
|
|
2064
|
+
<span style="font-size: 20px;">๐</span>
|
|
2065
|
+
</div>
|
|
2066
|
+
<div class="agent-title-info">
|
|
2067
|
+
<h3>Grep Search: ${this.escapeHtml(pattern)}</h3>
|
|
2068
|
+
<div class="agent-subtitle">
|
|
2069
|
+
<span class="tool-type-badge">${patternType}</span>
|
|
2070
|
+
<span class="tool-id-badge">ID: ${grepToolData.id ? grepToolData.id.slice(-8) : 'unknown'}</span>
|
|
2071
|
+
</div>
|
|
2072
|
+
</div>
|
|
2073
|
+
</div>
|
|
2074
|
+
</div>
|
|
2075
|
+
<button class="agent-modal-close" id="agent-modal-close">×</button>
|
|
2076
|
+
</div>
|
|
2077
|
+
|
|
2078
|
+
<div class="agent-modal-content">
|
|
2079
|
+
<!-- Primary Section: Tool Parameters -->
|
|
2080
|
+
<div class="raw-parameters-section primary-section">
|
|
2081
|
+
<h4>๐ง Tool Parameters</h4>
|
|
2082
|
+
<div class="raw-params-container">
|
|
2083
|
+
<pre class="raw-params-json">${this.escapeHtml(JSON.stringify(grepToolData.input, null, 2))}</pre>
|
|
2084
|
+
</div>
|
|
2085
|
+
</div>
|
|
2086
|
+
|
|
2087
|
+
<!-- Search Configuration -->
|
|
2088
|
+
<div class="grep-config-section">
|
|
2089
|
+
<h4>๐ฏ Search Configuration</h4>
|
|
2090
|
+
<div class="tool-details-grid">
|
|
2091
|
+
<div class="tool-detail-item">
|
|
2092
|
+
<span class="tool-detail-label">Search Pattern:</span>
|
|
2093
|
+
<span class="tool-detail-value search-pattern">${this.escapeHtml(pattern)}</span>
|
|
2094
|
+
</div>
|
|
2095
|
+
<div class="tool-detail-item">
|
|
2096
|
+
<span class="tool-detail-label">Pattern Type:</span>
|
|
2097
|
+
<span class="tool-detail-value">${patternType}</span>
|
|
2098
|
+
</div>
|
|
2099
|
+
<div class="tool-detail-item">
|
|
2100
|
+
<span class="tool-detail-label">Complexity:</span>
|
|
2101
|
+
<span class="tool-detail-value">${patternComplexity}</span>
|
|
2102
|
+
</div>
|
|
2103
|
+
<div class="tool-detail-item">
|
|
2104
|
+
<span class="tool-detail-label">Search Location:</span>
|
|
2105
|
+
<span class="tool-detail-value">${this.escapeHtml(searchPath)}</span>
|
|
2106
|
+
</div>
|
|
2107
|
+
<div class="tool-detail-item">
|
|
2108
|
+
<span class="tool-detail-label">Target Files:</span>
|
|
2109
|
+
<span class="tool-detail-value">${fileContext}</span>
|
|
2110
|
+
</div>
|
|
2111
|
+
<div class="tool-detail-item">
|
|
2112
|
+
<span class="tool-detail-label">Output Mode:</span>
|
|
2113
|
+
<span class="tool-detail-value">${outputMode.replace('_', ' ')}</span>
|
|
2114
|
+
</div>
|
|
2115
|
+
</div>
|
|
2116
|
+
</div>
|
|
2117
|
+
|
|
2118
|
+
<!-- Search Options -->
|
|
2119
|
+
<div class="grep-options-section">
|
|
2120
|
+
<h4>โ๏ธ Search Options</h4>
|
|
2121
|
+
<div class="search-options">
|
|
2122
|
+
${this.generateGrepOptionsDisplay(grepToolData.input)}
|
|
2123
|
+
</div>
|
|
2124
|
+
</div>
|
|
2125
|
+
|
|
2126
|
+
<!-- Pattern Analysis -->
|
|
2127
|
+
<div class="grep-pattern-section">
|
|
2128
|
+
<h4>๐งฉ Pattern Analysis</h4>
|
|
2129
|
+
<div class="pattern-analysis">
|
|
2130
|
+
${this.analyzeGrepPattern(pattern)}
|
|
2131
|
+
</div>
|
|
2132
|
+
</div>
|
|
2133
|
+
|
|
2134
|
+
<!-- Search Insights -->
|
|
2135
|
+
<div class="grep-insights-section">
|
|
2136
|
+
<h4>๐ Search Insights</h4>
|
|
2137
|
+
<div class="tool-insights">
|
|
2138
|
+
<div class="insight-item">
|
|
2139
|
+
<span class="insight-label">Search Scope:</span>
|
|
2140
|
+
<span class="insight-value">${searchScope}</span>
|
|
2141
|
+
</div>
|
|
2142
|
+
<div class="insight-item">
|
|
2143
|
+
<span class="insight-label">Case Sensitivity:</span>
|
|
2144
|
+
<span class="insight-value">${caseInsensitive ? 'Case insensitive' : 'Case sensitive'}</span>
|
|
2145
|
+
</div>
|
|
2146
|
+
<div class="insight-item">
|
|
2147
|
+
<span class="insight-label">Multiline Mode:</span>
|
|
2148
|
+
<span class="insight-value">${multiline ? 'Enabled (cross-line patterns)' : 'Disabled (single-line only)'}</span>
|
|
2149
|
+
</div>
|
|
2150
|
+
<div class="insight-item">
|
|
2151
|
+
<span class="insight-label">Context Lines:</span>
|
|
2152
|
+
<span class="insight-value">${contextAround > 0 ? `${contextAround} lines around` : contextBefore > 0 || contextAfter > 0 ? `${contextBefore} before, ${contextAfter} after` : 'None'}</span>
|
|
2153
|
+
</div>
|
|
2154
|
+
</div>
|
|
2155
|
+
</div>
|
|
2156
|
+
</div>
|
|
2157
|
+
`;
|
|
2158
|
+
|
|
2159
|
+
const modalHTML = `
|
|
2160
|
+
<div class="agent-modal-overlay" id="agent-modal-overlay">
|
|
2161
|
+
<div class="agent-modal grep-tool-modal">
|
|
2162
|
+
${modalContent}
|
|
2163
|
+
</div>
|
|
2164
|
+
</div>
|
|
2165
|
+
`;
|
|
2166
|
+
|
|
2167
|
+
// Add modal to DOM
|
|
2168
|
+
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
|
2169
|
+
|
|
2170
|
+
// Bind close events
|
|
2171
|
+
document.getElementById('agent-modal-close').addEventListener('click', () => this.closeAgentModal());
|
|
2172
|
+
document.getElementById('agent-modal-overlay').addEventListener('click', (e) => {
|
|
2173
|
+
if (e.target.id === 'agent-modal-overlay') {
|
|
2174
|
+
this.closeAgentModal();
|
|
2175
|
+
}
|
|
2176
|
+
});
|
|
2177
|
+
|
|
2178
|
+
// ESC key to close - store reference for cleanup
|
|
2179
|
+
this.modalKeydownHandler = (e) => {
|
|
2180
|
+
if (e.key === 'Escape') {
|
|
2181
|
+
this.closeAgentModal();
|
|
2182
|
+
}
|
|
2183
|
+
};
|
|
2184
|
+
document.addEventListener('keydown', this.modalKeydownHandler);
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
/**
|
|
2188
|
+
* Generate Grep options display
|
|
2189
|
+
* @param {Object} input - Grep input parameters
|
|
2190
|
+
* @returns {string} HTML for options display
|
|
2191
|
+
*/
|
|
2192
|
+
generateGrepOptionsDisplay(input) {
|
|
2193
|
+
const options = [];
|
|
2194
|
+
|
|
2195
|
+
if (input['-i']) options.push({ name: 'Case Insensitive (-i)', status: 'enabled', desc: 'Ignore case when matching' });
|
|
2196
|
+
if (input['-n']) options.push({ name: 'Line Numbers (-n)', status: 'enabled', desc: 'Show line numbers in output' });
|
|
2197
|
+
if (input.multiline) options.push({ name: 'Multiline Mode', status: 'enabled', desc: 'Allow patterns to span multiple lines' });
|
|
2198
|
+
if (input['-A']) options.push({ name: `After Context (-A ${input['-A']})`, status: 'enabled', desc: `Show ${input['-A']} lines after matches` });
|
|
2199
|
+
if (input['-B']) options.push({ name: `Before Context (-B ${input['-B']})`, status: 'enabled', desc: `Show ${input['-B']} lines before matches` });
|
|
2200
|
+
if (input['-C']) options.push({ name: `Around Context (-C ${input['-C']})`, status: 'enabled', desc: `Show ${input['-C']} lines around matches` });
|
|
2201
|
+
if (input.glob) options.push({ name: `File Filter (--glob)`, status: 'enabled', desc: `Only search files matching "${input.glob}"` });
|
|
2202
|
+
if (input.type) options.push({ name: `File Type (--type)`, status: 'enabled', desc: `Only search ${input.type} files` });
|
|
2203
|
+
if (input.head_limit) options.push({ name: `Result Limit`, status: 'enabled', desc: `Limit to first ${input.head_limit} results` });
|
|
2204
|
+
|
|
2205
|
+
if (options.length === 0) {
|
|
2206
|
+
return '<div class="no-options">Using default search options</div>';
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
return options.map(option => `
|
|
2210
|
+
<div class="search-option ${option.status}">
|
|
2211
|
+
<div class="option-header">
|
|
2212
|
+
<span class="option-name">${option.name}</span>
|
|
2213
|
+
<span class="option-status ${option.status}">${option.status}</span>
|
|
2214
|
+
</div>
|
|
2215
|
+
<div class="option-desc">${option.desc}</div>
|
|
2216
|
+
</div>
|
|
2217
|
+
`).join('');
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
/**
|
|
2221
|
+
* Analyze grep pattern components
|
|
2222
|
+
* @param {string} pattern - Grep pattern to analyze
|
|
2223
|
+
* @returns {string} HTML breakdown of pattern
|
|
2224
|
+
*/
|
|
2225
|
+
analyzeGrepPattern(pattern) {
|
|
2226
|
+
if (!pattern) return '<div class="pattern-empty">No pattern specified</div>';
|
|
2227
|
+
|
|
2228
|
+
const analysis = [];
|
|
2229
|
+
|
|
2230
|
+
// Detect regex components
|
|
2231
|
+
if (pattern.includes('.*')) {
|
|
2232
|
+
analysis.push({ component: '.*', desc: 'Match any characters (zero or more)', type: 'regex' });
|
|
2233
|
+
}
|
|
2234
|
+
if (pattern.includes('.+')) {
|
|
2235
|
+
analysis.push({ component: '.+', desc: 'Match any characters (one or more)', type: 'regex' });
|
|
2236
|
+
}
|
|
2237
|
+
if (pattern.includes('\\w')) {
|
|
2238
|
+
analysis.push({ component: '\\w', desc: 'Match word characters (letters, digits, underscore)', type: 'regex' });
|
|
2239
|
+
}
|
|
2240
|
+
if (pattern.includes('\\d')) {
|
|
2241
|
+
analysis.push({ component: '\\d', desc: 'Match digit characters (0-9)', type: 'regex' });
|
|
2242
|
+
}
|
|
2243
|
+
if (pattern.includes('\\s')) {
|
|
2244
|
+
analysis.push({ component: '\\s', desc: 'Match whitespace characters', type: 'regex' });
|
|
2245
|
+
}
|
|
2246
|
+
if (pattern.includes('^')) {
|
|
2247
|
+
analysis.push({ component: '^', desc: 'Match start of line', type: 'anchor' });
|
|
2248
|
+
}
|
|
2249
|
+
if (pattern.includes('$')) {
|
|
2250
|
+
analysis.push({ component: '$', desc: 'Match end of line', type: 'anchor' });
|
|
2251
|
+
}
|
|
2252
|
+
if (pattern.includes('|')) {
|
|
2253
|
+
analysis.push({ component: '|', desc: 'Logical OR (alternative patterns)', type: 'operator' });
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
// Detect character classes
|
|
2257
|
+
const charClassMatch = pattern.match(/\[([^\]]+)\]/g);
|
|
2258
|
+
if (charClassMatch) {
|
|
2259
|
+
charClassMatch.forEach(match => {
|
|
2260
|
+
analysis.push({ component: match, desc: `Match any character in the set: ${match}`, type: 'charclass' });
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
// Detect groups
|
|
2265
|
+
const groupMatch = pattern.match(/\(([^)]+)\)/g);
|
|
2266
|
+
if (groupMatch) {
|
|
2267
|
+
groupMatch.forEach(match => {
|
|
2268
|
+
analysis.push({ component: match, desc: `Capture group: ${match}`, type: 'group' });
|
|
2269
|
+
});
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
if (analysis.length === 0) {
|
|
2273
|
+
return '<div class="pattern-simple">Simple literal text search</div>';
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
return analysis.map(item => `
|
|
2277
|
+
<div class="pattern-component ${item.type}">
|
|
2278
|
+
<code class="pattern-part">${this.escapeHtml(item.component)}</code>
|
|
2279
|
+
<span class="pattern-desc">${item.desc}</span>
|
|
2280
|
+
</div>
|
|
2281
|
+
`).join('');
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
/**
|
|
2285
|
+
* Open TodoWrite tool specific modal
|
|
2286
|
+
* @param {Object} todoToolData - TodoWrite tool data
|
|
2287
|
+
*/
|
|
2288
|
+
openTodoWriteToolModal(todoToolData) {
|
|
2289
|
+
console.log('๐ Opening TodoWrite tool modal with data:', todoToolData);
|
|
2290
|
+
|
|
2291
|
+
if (!todoToolData || !todoToolData.input) {
|
|
2292
|
+
console.warn('โ ๏ธ TodoWrite tool data missing or invalid');
|
|
2293
|
+
return;
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
// Extract TodoWrite parameters
|
|
2297
|
+
const todos = todoToolData.input.todos || [];
|
|
2298
|
+
const todoCount = todos.length;
|
|
2299
|
+
|
|
2300
|
+
// Analyze todos
|
|
2301
|
+
const statusCounts = {
|
|
2302
|
+
pending: 0,
|
|
2303
|
+
in_progress: 0,
|
|
2304
|
+
completed: 0
|
|
2305
|
+
};
|
|
2306
|
+
|
|
2307
|
+
const priorityCounts = {
|
|
2308
|
+
high: 0,
|
|
2309
|
+
medium: 0,
|
|
2310
|
+
low: 0
|
|
2311
|
+
};
|
|
2312
|
+
|
|
2313
|
+
todos.forEach(todo => {
|
|
2314
|
+
if (statusCounts.hasOwnProperty(todo.status)) {
|
|
2315
|
+
statusCounts[todo.status]++;
|
|
2316
|
+
}
|
|
2317
|
+
if (priorityCounts.hasOwnProperty(todo.priority)) {
|
|
2318
|
+
priorityCounts[todo.priority]++;
|
|
2319
|
+
}
|
|
2320
|
+
});
|
|
2321
|
+
|
|
2322
|
+
// Calculate completion rate
|
|
2323
|
+
const completionRate = todoCount > 0 ? Math.round((statusCounts.completed / todoCount) * 100) : 0;
|
|
2324
|
+
|
|
2325
|
+
// Find longest and shortest todos
|
|
2326
|
+
const todoLengths = todos.map(todo => todo.content.length);
|
|
2327
|
+
const avgLength = todoLengths.length > 0 ? Math.round(todoLengths.reduce((a, b) => a + b, 0) / todoLengths.length) : 0;
|
|
2328
|
+
|
|
2329
|
+
const modalContent = `
|
|
2330
|
+
<div class="agent-modal-header">
|
|
2331
|
+
<div class="agent-modal-title">
|
|
2332
|
+
<div class="agent-title-main">
|
|
2333
|
+
<div class="tool-icon todo-tool">
|
|
2334
|
+
<span style="font-size: 20px;">๐</span>
|
|
2335
|
+
</div>
|
|
2336
|
+
<div class="agent-title-info">
|
|
2337
|
+
<h3>Todo Management: ${todoCount} tasks</h3>
|
|
2338
|
+
<div class="agent-subtitle">
|
|
2339
|
+
<span class="tool-type-badge">${completionRate}% Complete</span>
|
|
2340
|
+
<span class="tool-id-badge">ID: ${todoToolData.id ? todoToolData.id.slice(-8) : 'unknown'}</span>
|
|
2341
|
+
</div>
|
|
2342
|
+
</div>
|
|
2343
|
+
</div>
|
|
2344
|
+
</div>
|
|
2345
|
+
<button class="agent-modal-close" id="agent-modal-close">×</button>
|
|
2346
|
+
</div>
|
|
2347
|
+
|
|
2348
|
+
<div class="agent-modal-content">
|
|
2349
|
+
<!-- Primary Section: Tool Parameters -->
|
|
2350
|
+
<div class="raw-parameters-section primary-section">
|
|
2351
|
+
<h4>๐ง Tool Parameters</h4>
|
|
2352
|
+
<div class="raw-params-container">
|
|
2353
|
+
<pre class="raw-params-json">${this.escapeHtml(JSON.stringify(todoToolData.input, null, 2))}</pre>
|
|
2354
|
+
</div>
|
|
2355
|
+
</div>
|
|
2356
|
+
|
|
2357
|
+
<!-- Todo Summary -->
|
|
2358
|
+
<div class="todo-summary-section">
|
|
2359
|
+
<h4>๐ Todo Summary</h4>
|
|
2360
|
+
<div class="todo-summary">
|
|
2361
|
+
<div class="summary-stats">
|
|
2362
|
+
<div class="stat-item">
|
|
2363
|
+
<span class="stat-number">${todoCount}</span>
|
|
2364
|
+
<span class="stat-label">Total Todos</span>
|
|
2365
|
+
</div>
|
|
2366
|
+
<div class="stat-item">
|
|
2367
|
+
<span class="stat-number">${completionRate}%</span>
|
|
2368
|
+
<span class="stat-label">Completion Rate</span>
|
|
2369
|
+
</div>
|
|
2370
|
+
<div class="stat-item">
|
|
2371
|
+
<span class="stat-number">${avgLength}</span>
|
|
2372
|
+
<span class="stat-label">Avg Length</span>
|
|
2373
|
+
</div>
|
|
2374
|
+
</div>
|
|
2375
|
+
</div>
|
|
2376
|
+
</div>
|
|
2377
|
+
|
|
2378
|
+
<!-- Status Breakdown -->
|
|
2379
|
+
<div class="todo-status-section">
|
|
2380
|
+
<h4>๐ Status Breakdown</h4>
|
|
2381
|
+
<div class="status-breakdown">
|
|
2382
|
+
<div class="status-item pending">
|
|
2383
|
+
<div class="status-info">
|
|
2384
|
+
<span class="status-name">Pending</span>
|
|
2385
|
+
<span class="status-count">${statusCounts.pending}</span>
|
|
2386
|
+
</div>
|
|
2387
|
+
<div class="status-bar">
|
|
2388
|
+
<div class="status-fill" style="width: ${todoCount > 0 ? (statusCounts.pending / todoCount) * 100 : 0}%"></div>
|
|
2389
|
+
</div>
|
|
2390
|
+
</div>
|
|
2391
|
+
<div class="status-item in-progress">
|
|
2392
|
+
<div class="status-info">
|
|
2393
|
+
<span class="status-name">In Progress</span>
|
|
2394
|
+
<span class="status-count">${statusCounts.in_progress}</span>
|
|
2395
|
+
</div>
|
|
2396
|
+
<div class="status-bar">
|
|
2397
|
+
<div class="status-fill" style="width: ${todoCount > 0 ? (statusCounts.in_progress / todoCount) * 100 : 0}%"></div>
|
|
2398
|
+
</div>
|
|
2399
|
+
</div>
|
|
2400
|
+
<div class="status-item completed">
|
|
2401
|
+
<div class="status-info">
|
|
2402
|
+
<span class="status-name">Completed</span>
|
|
2403
|
+
<span class="status-count">${statusCounts.completed}</span>
|
|
2404
|
+
</div>
|
|
2405
|
+
<div class="status-bar">
|
|
2406
|
+
<div class="status-fill" style="width: ${todoCount > 0 ? (statusCounts.completed / todoCount) * 100 : 0}%"></div>
|
|
2407
|
+
</div>
|
|
2408
|
+
</div>
|
|
2409
|
+
</div>
|
|
2410
|
+
</div>
|
|
2411
|
+
|
|
2412
|
+
<!-- Priority Distribution -->
|
|
2413
|
+
<div class="todo-priority-section">
|
|
2414
|
+
<h4>๐ฏ Priority Distribution</h4>
|
|
2415
|
+
<div class="priority-distribution">
|
|
2416
|
+
<div class="priority-item high">
|
|
2417
|
+
<span class="priority-label">High Priority</span>
|
|
2418
|
+
<span class="priority-count">${priorityCounts.high}</span>
|
|
2419
|
+
</div>
|
|
2420
|
+
<div class="priority-item medium">
|
|
2421
|
+
<span class="priority-label">Medium Priority</span>
|
|
2422
|
+
<span class="priority-count">${priorityCounts.medium}</span>
|
|
2423
|
+
</div>
|
|
2424
|
+
<div class="priority-item low">
|
|
2425
|
+
<span class="priority-label">Low Priority</span>
|
|
2426
|
+
<span class="priority-count">${priorityCounts.low}</span>
|
|
2427
|
+
</div>
|
|
2428
|
+
</div>
|
|
2429
|
+
</div>
|
|
2430
|
+
|
|
2431
|
+
<!-- Todo Items -->
|
|
2432
|
+
<div class="todo-items-section">
|
|
2433
|
+
<h4>๐ Todo Items</h4>
|
|
2434
|
+
<div class="todo-items">
|
|
2435
|
+
${this.generateTodoItemsDisplay(todos)}
|
|
2436
|
+
</div>
|
|
2437
|
+
</div>
|
|
2438
|
+
</div>
|
|
2439
|
+
`;
|
|
2440
|
+
|
|
2441
|
+
const modalHTML = `
|
|
2442
|
+
<div class="agent-modal-overlay" id="agent-modal-overlay">
|
|
2443
|
+
<div class="agent-modal todo-tool-modal">
|
|
2444
|
+
${modalContent}
|
|
2445
|
+
</div>
|
|
2446
|
+
</div>
|
|
2447
|
+
`;
|
|
2448
|
+
|
|
2449
|
+
// Add modal to DOM
|
|
2450
|
+
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
|
2451
|
+
|
|
2452
|
+
// Bind close events
|
|
2453
|
+
document.getElementById('agent-modal-close').addEventListener('click', () => this.closeAgentModal());
|
|
2454
|
+
document.getElementById('agent-modal-overlay').addEventListener('click', (e) => {
|
|
2455
|
+
if (e.target.id === 'agent-modal-overlay') {
|
|
2456
|
+
this.closeAgentModal();
|
|
2457
|
+
}
|
|
2458
|
+
});
|
|
2459
|
+
|
|
2460
|
+
// ESC key to close - store reference for cleanup
|
|
2461
|
+
this.modalKeydownHandler = (e) => {
|
|
2462
|
+
if (e.key === 'Escape') {
|
|
2463
|
+
this.closeAgentModal();
|
|
2464
|
+
}
|
|
2465
|
+
};
|
|
2466
|
+
document.addEventListener('keydown', this.modalKeydownHandler);
|
|
2467
|
+
}
|
|
2468
|
+
|
|
2469
|
+
/**
|
|
2470
|
+
* Generate todo items display
|
|
2471
|
+
* @param {Array} todos - Array of todo items
|
|
2472
|
+
* @returns {string} HTML for todo items
|
|
2473
|
+
*/
|
|
2474
|
+
generateTodoItemsDisplay(todos) {
|
|
2475
|
+
if (!todos || todos.length === 0) {
|
|
2476
|
+
return '<div class="no-todos">No todos found</div>';
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
return todos.map((todo, index) => `
|
|
2480
|
+
<div class="todo-item ${todo.status} ${todo.priority}">
|
|
2481
|
+
<div class="todo-header">
|
|
2482
|
+
<span class="todo-index">#${index + 1}</span>
|
|
2483
|
+
<span class="todo-status ${todo.status}">${todo.status.replace('_', ' ')}</span>
|
|
2484
|
+
<span class="todo-priority ${todo.priority}">${todo.priority}</span>
|
|
2485
|
+
</div>
|
|
2486
|
+
<div class="todo-content">${this.escapeHtml(todo.content)}</div>
|
|
2487
|
+
<div class="todo-meta">
|
|
2488
|
+
<span class="todo-id">ID: ${todo.id}</span>
|
|
2489
|
+
<span class="todo-length">${todo.content.length} chars</span>
|
|
2490
|
+
</div>
|
|
2491
|
+
</div>
|
|
2492
|
+
`).join('');
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
/**
|
|
2496
|
+
* Open Bash tool specific modal
|
|
2497
|
+
* @param {Object} bashToolData - Bash tool data
|
|
2498
|
+
*/
|
|
2499
|
+
openBashToolModal(bashToolData) {
|
|
2500
|
+
const input = bashToolData.input || {};
|
|
2501
|
+
const command = input.command || 'Unknown command';
|
|
2502
|
+
const description = input.description || '';
|
|
2503
|
+
const timeout = input.timeout || 120000; // Default 2 minutes
|
|
2504
|
+
const toolId = bashToolData.id || 'unknown';
|
|
2505
|
+
|
|
2506
|
+
// Analyze command type
|
|
2507
|
+
const isGitCommand = command.startsWith('git ');
|
|
2508
|
+
const isNpmCommand = command.startsWith('npm ') || command.startsWith('yarn ') || command.startsWith('pnpm ');
|
|
2509
|
+
const isFileCommand = command.includes('ls') || command.includes('cat') || command.includes('find') || command.includes('grep');
|
|
2510
|
+
const isBuildCommand = command.includes('build') || command.includes('compile') || command.includes('make');
|
|
2511
|
+
const isTestCommand = command.includes('test') || command.includes('jest') || command.includes('mocha');
|
|
2512
|
+
const isSystemCommand = command.includes('ps') || command.includes('kill') || command.includes('sudo');
|
|
2513
|
+
|
|
2514
|
+
let commandCategory = '';
|
|
2515
|
+
let contextIcon = 'โก';
|
|
2516
|
+
|
|
2517
|
+
if (isGitCommand) {
|
|
2518
|
+
commandCategory = 'Git Operation';
|
|
2519
|
+
contextIcon = '๐ง';
|
|
2520
|
+
} else if (isNpmCommand) {
|
|
2521
|
+
commandCategory = 'Package Management';
|
|
2522
|
+
contextIcon = '๐ฆ';
|
|
2523
|
+
} else if (isBuildCommand) {
|
|
2524
|
+
commandCategory = 'Build Process';
|
|
2525
|
+
contextIcon = '๐จ';
|
|
2526
|
+
} else if (isTestCommand) {
|
|
2527
|
+
commandCategory = 'Testing';
|
|
2528
|
+
contextIcon = '๐งช';
|
|
2529
|
+
} else if (isFileCommand) {
|
|
2530
|
+
commandCategory = 'File Operations';
|
|
2531
|
+
contextIcon = '๐';
|
|
2532
|
+
} else if (isSystemCommand) {
|
|
2533
|
+
commandCategory = 'System Command';
|
|
2534
|
+
contextIcon = '๐ฅ๏ธ';
|
|
2535
|
+
} else {
|
|
2536
|
+
commandCategory = 'Shell Command';
|
|
2537
|
+
contextIcon = 'โก';
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
// Parse command for better display
|
|
2541
|
+
const commandParts = command.split(' ');
|
|
2542
|
+
const mainCommand = commandParts[0] || '';
|
|
2543
|
+
const args = commandParts.slice(1).join(' ') || '';
|
|
2544
|
+
|
|
2545
|
+
const modalHTML = `
|
|
2546
|
+
<div class="agent-modal-overlay" id="agent-modal-overlay">
|
|
2547
|
+
<div class="agent-modal bash-tool-modal">
|
|
2548
|
+
<div class="agent-modal-header">
|
|
2549
|
+
<div class="agent-modal-title">
|
|
2550
|
+
<div class="agent-title-main">
|
|
2551
|
+
<div class="tool-icon bash-tool">
|
|
2552
|
+
<span style="font-size: 20px;">${contextIcon}</span>
|
|
2553
|
+
</div>
|
|
2554
|
+
<div class="agent-title-info">
|
|
2555
|
+
<h3>Command: ${mainCommand}</h3>
|
|
2556
|
+
<div class="agent-subtitle">
|
|
2557
|
+
<span class="tool-type-badge">${commandCategory}</span>
|
|
2558
|
+
<span class="tool-id-badge">ID: ${toolId.slice(-8)}</span>
|
|
2559
|
+
</div>
|
|
2560
|
+
</div>
|
|
2561
|
+
</div>
|
|
734
2562
|
</div>
|
|
2563
|
+
<button class="agent-modal-close" id="agent-modal-close">×</button>
|
|
735
2564
|
</div>
|
|
736
2565
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
<div class="
|
|
741
|
-
<
|
|
742
|
-
<div class="selected-conversation-meta" id="selected-conversation-meta"></div>
|
|
2566
|
+
<div class="agent-modal-content">
|
|
2567
|
+
<div class="raw-parameters-section primary-section">
|
|
2568
|
+
<h4>๐ง Tool Parameters</h4>
|
|
2569
|
+
<div class="raw-params-container">
|
|
2570
|
+
<pre class="raw-params-json">${JSON.stringify(input, null, 2)}</pre>
|
|
743
2571
|
</div>
|
|
744
|
-
<div class="
|
|
745
|
-
<
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
</
|
|
2572
|
+
<div class="params-summary">
|
|
2573
|
+
<span class="param-chip">Tool ID: ${toolId.slice(-8)}</span>
|
|
2574
|
+
<span class="param-chip">Command: ${mainCommand}</span>
|
|
2575
|
+
<span class="param-chip">Timeout: ${timeout/1000}s</span>
|
|
2576
|
+
${description ? `<span class="param-chip">Described: Yes</span>` : `<span class="param-chip">Described: No</span>`}
|
|
749
2577
|
</div>
|
|
750
2578
|
</div>
|
|
751
|
-
|
|
752
|
-
<div class="
|
|
753
|
-
<div class="
|
|
754
|
-
<
|
|
755
|
-
<
|
|
756
|
-
|
|
2579
|
+
|
|
2580
|
+
<div class="command-execution-section">
|
|
2581
|
+
<div class="command-header-section">
|
|
2582
|
+
<h4>๐ป Command Execution</h4>
|
|
2583
|
+
<div class="command-stats">
|
|
2584
|
+
<span class="command-stat">${commandParts.length} parts</span>
|
|
2585
|
+
<span class="command-stat">${command.length} chars</span>
|
|
2586
|
+
<span class="command-stat">${timeout/1000}s timeout</span>
|
|
2587
|
+
</div>
|
|
2588
|
+
</div>
|
|
2589
|
+
<div class="command-display-container">
|
|
2590
|
+
<div class="command-line">
|
|
2591
|
+
<span class="command-prompt">$</span>
|
|
2592
|
+
<span class="command-text">${this.escapeHtml(command)}</span>
|
|
2593
|
+
</div>
|
|
2594
|
+
${description ? `
|
|
2595
|
+
<div class="command-description">
|
|
2596
|
+
<span class="description-label">Description:</span>
|
|
2597
|
+
<span class="description-text">${this.escapeHtml(description)}</span>
|
|
2598
|
+
</div>
|
|
2599
|
+
` : ''}
|
|
757
2600
|
</div>
|
|
758
2601
|
</div>
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
<div class="
|
|
763
|
-
<
|
|
764
|
-
|
|
2602
|
+
|
|
2603
|
+
<div class="command-analysis-section">
|
|
2604
|
+
<h4>๐ Command Analysis</h4>
|
|
2605
|
+
<div class="analysis-grid">
|
|
2606
|
+
<div class="analysis-item">
|
|
2607
|
+
<span class="analysis-label">Main Command:</span>
|
|
2608
|
+
<code class="analysis-value">${this.escapeHtml(mainCommand)}</code>
|
|
2609
|
+
</div>
|
|
2610
|
+
${args ? `
|
|
2611
|
+
<div class="analysis-item">
|
|
2612
|
+
<span class="analysis-label">Arguments:</span>
|
|
2613
|
+
<code class="analysis-value">${this.escapeHtml(args)}</code>
|
|
2614
|
+
</div>
|
|
2615
|
+
` : ''}
|
|
2616
|
+
<div class="analysis-item">
|
|
2617
|
+
<span class="analysis-label">Category:</span>
|
|
2618
|
+
<code class="analysis-value">${commandCategory}</code>
|
|
2619
|
+
</div>
|
|
2620
|
+
<div class="analysis-item">
|
|
2621
|
+
<span class="analysis-label">Timeout:</span>
|
|
2622
|
+
<code class="analysis-value">${timeout === 120000 ? 'Default (2min)' : `${timeout/1000}s`}</code>
|
|
2623
|
+
</div>
|
|
765
2624
|
</div>
|
|
766
|
-
<div class="state-timestamp" id="state-timestamp"></div>
|
|
767
2625
|
</div>
|
|
768
|
-
</div>
|
|
769
|
-
</div>
|
|
770
2626
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
2627
|
+
<div class="file-insights-section">
|
|
2628
|
+
<h4>๐ Execution Insights</h4>
|
|
2629
|
+
<div class="insights-grid">
|
|
2630
|
+
<div class="insight-card">
|
|
2631
|
+
<div class="insight-header">
|
|
2632
|
+
<span class="insight-icon">${contextIcon}</span>
|
|
2633
|
+
<span class="insight-title">Command Type</span>
|
|
2634
|
+
</div>
|
|
2635
|
+
<div class="insight-content">${commandCategory}</div>
|
|
2636
|
+
</div>
|
|
2637
|
+
<div class="insight-card">
|
|
2638
|
+
<div class="insight-header">
|
|
2639
|
+
<span class="insight-icon">โฑ๏ธ</span>
|
|
2640
|
+
<span class="insight-title">Timeout</span>
|
|
2641
|
+
</div>
|
|
2642
|
+
<div class="insight-content">${timeout/1000} seconds</div>
|
|
2643
|
+
</div>
|
|
2644
|
+
<div class="insight-card">
|
|
2645
|
+
<div class="insight-header">
|
|
2646
|
+
<span class="insight-icon">๐</span>
|
|
2647
|
+
<span class="insight-title">Complexity</span>
|
|
2648
|
+
</div>
|
|
2649
|
+
<div class="insight-content">${commandParts.length < 3 ? 'Simple' : commandParts.length < 6 ? 'Medium' : 'Complex'}</div>
|
|
2650
|
+
</div>
|
|
2651
|
+
<div class="insight-card">
|
|
2652
|
+
<div class="insight-header">
|
|
2653
|
+
<span class="insight-icon">๐ฏ</span>
|
|
2654
|
+
<span class="insight-title">Documentation</span>
|
|
2655
|
+
</div>
|
|
2656
|
+
<div class="insight-content">${description ? 'Documented' : 'No description'}</div>
|
|
2657
|
+
</div>
|
|
2658
|
+
</div>
|
|
2659
|
+
</div>
|
|
778
2660
|
</div>
|
|
779
2661
|
</div>
|
|
780
2662
|
</div>
|
|
781
2663
|
`;
|
|
2664
|
+
|
|
2665
|
+
// Add modal to DOM
|
|
2666
|
+
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
|
2667
|
+
|
|
2668
|
+
// Bind close events
|
|
2669
|
+
document.getElementById('agent-modal-close').addEventListener('click', () => this.closeAgentModal());
|
|
2670
|
+
document.getElementById('agent-modal-overlay').addEventListener('click', (e) => {
|
|
2671
|
+
if (e.target.id === 'agent-modal-overlay') {
|
|
2672
|
+
this.closeAgentModal();
|
|
2673
|
+
}
|
|
2674
|
+
});
|
|
2675
|
+
|
|
2676
|
+
// ESC key to close
|
|
2677
|
+
this.modalKeydownHandler = (e) => {
|
|
2678
|
+
if (e.key === 'Escape') {
|
|
2679
|
+
this.closeAgentModal();
|
|
2680
|
+
}
|
|
2681
|
+
};
|
|
2682
|
+
document.addEventListener('keydown', this.modalKeydownHandler);
|
|
2683
|
+
}
|
|
782
2684
|
|
|
783
|
-
|
|
784
|
-
|
|
2685
|
+
/**
|
|
2686
|
+
* Escape HTML to prevent XSS
|
|
2687
|
+
* @param {string} text - Text to escape
|
|
2688
|
+
* @returns {string} Escaped text
|
|
2689
|
+
*/
|
|
2690
|
+
escapeHtml(text) {
|
|
2691
|
+
if (typeof text !== 'string') return String(text);
|
|
2692
|
+
|
|
2693
|
+
const div = document.createElement('div');
|
|
2694
|
+
div.textContent = text;
|
|
2695
|
+
return div.innerHTML;
|
|
785
2696
|
}
|
|
786
2697
|
|
|
787
2698
|
/**
|
|
788
|
-
*
|
|
2699
|
+
* Close agent modal
|
|
789
2700
|
*/
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
this.stateService
|
|
799
|
-
);
|
|
800
|
-
await this.components.conversationTable.initialize();
|
|
801
|
-
} catch (error) {
|
|
802
|
-
console.warn('ConversationTable initialization failed:', error);
|
|
803
|
-
// Show fallback content
|
|
804
|
-
tableContainer.innerHTML = `
|
|
805
|
-
<div class="conversation-table-placeholder">
|
|
806
|
-
<p>Detailed table view not available</p>
|
|
807
|
-
</div>
|
|
808
|
-
`;
|
|
809
|
-
}
|
|
2701
|
+
closeAgentModal() {
|
|
2702
|
+
const modal = document.getElementById('agent-modal-overlay');
|
|
2703
|
+
if (modal) {
|
|
2704
|
+
modal.remove();
|
|
2705
|
+
}
|
|
2706
|
+
if (this.modalKeydownHandler) {
|
|
2707
|
+
document.removeEventListener('keydown', this.modalKeydownHandler);
|
|
2708
|
+
this.modalKeydownHandler = null;
|
|
810
2709
|
}
|
|
811
2710
|
}
|
|
812
2711
|
|
|
813
2712
|
/**
|
|
814
|
-
*
|
|
2713
|
+
* Show agents loading state
|
|
2714
|
+
* @param {boolean} show - Whether to show loading
|
|
815
2715
|
*/
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
const
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
searchInput.addEventListener('input', (e) => this.updateFilter('search', e.target.value));
|
|
826
|
-
|
|
827
|
-
const clearSearch = this.container.querySelector('#clear-search');
|
|
828
|
-
clearSearch.addEventListener('click', () => this.clearSearch());
|
|
2716
|
+
showAgentsLoading(show) {
|
|
2717
|
+
const loading = this.container.querySelector('#agents-loading');
|
|
2718
|
+
const list = this.container.querySelector('#agents-list');
|
|
2719
|
+
|
|
2720
|
+
if (loading && list) {
|
|
2721
|
+
loading.style.display = show ? 'flex' : 'none';
|
|
2722
|
+
list.style.display = show ? 'none' : 'block';
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
829
2725
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
2726
|
+
/**
|
|
2727
|
+
* Show agents empty state
|
|
2728
|
+
*/
|
|
2729
|
+
showAgentsEmpty() {
|
|
2730
|
+
const empty = this.container.querySelector('#agents-empty');
|
|
2731
|
+
const list = this.container.querySelector('#agents-list');
|
|
2732
|
+
const count = this.container.querySelector('#agents-count');
|
|
2733
|
+
|
|
2734
|
+
if (empty && list && count) {
|
|
2735
|
+
empty.style.display = 'flex';
|
|
2736
|
+
list.style.display = 'none';
|
|
2737
|
+
count.textContent = '0 agents';
|
|
834
2738
|
}
|
|
2739
|
+
}
|
|
835
2740
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
2741
|
+
/**
|
|
2742
|
+
* Hide agents empty state
|
|
2743
|
+
*/
|
|
2744
|
+
hideAgentsEmpty() {
|
|
2745
|
+
const empty = this.container.querySelector('#agents-empty');
|
|
2746
|
+
if (empty) {
|
|
2747
|
+
empty.style.display = 'none';
|
|
840
2748
|
}
|
|
2749
|
+
}
|
|
841
2750
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
2751
|
+
/**
|
|
2752
|
+
* Get project-specific agents for a conversation
|
|
2753
|
+
* @param {string} conversationId - Conversation ID
|
|
2754
|
+
* @returns {Array} Array of project agents for this conversation
|
|
2755
|
+
*/
|
|
2756
|
+
getAgentsForConversation(conversationId) {
|
|
2757
|
+
const conversations = this.stateService.getStateProperty('conversations') || [];
|
|
2758
|
+
const conversation = conversations.find(conv => conv.id === conversationId);
|
|
2759
|
+
|
|
2760
|
+
if (!conversation || !conversation.project) {
|
|
2761
|
+
// Return empty array if no project (global agents are shown in main section)
|
|
2762
|
+
return [];
|
|
846
2763
|
}
|
|
2764
|
+
|
|
2765
|
+
const projectName = conversation.project;
|
|
2766
|
+
|
|
2767
|
+
// Return only project agents for this specific project
|
|
2768
|
+
return this.agents.filter(agent =>
|
|
2769
|
+
agent.level === 'project' && agent.projectName === projectName
|
|
2770
|
+
);
|
|
847
2771
|
}
|
|
848
|
-
|
|
2772
|
+
|
|
849
2773
|
/**
|
|
850
|
-
*
|
|
2774
|
+
* Show project agents modal
|
|
2775
|
+
* @param {string} conversationId - Conversation ID
|
|
851
2776
|
*/
|
|
852
|
-
|
|
853
|
-
const
|
|
854
|
-
|
|
2777
|
+
showProjectAgents(conversationId) {
|
|
2778
|
+
const conversations = this.stateService.getStateProperty('conversations') || [];
|
|
2779
|
+
const conversation = conversations.find(conv => conv.id === conversationId);
|
|
2780
|
+
const projectAgents = this.getAgentsForConversation(conversationId);
|
|
2781
|
+
|
|
2782
|
+
const projectName = conversation?.project || 'Unknown Project';
|
|
2783
|
+
const chatTitle = conversation?.title || `Chat ${conversationId.slice(-8)}`;
|
|
2784
|
+
|
|
2785
|
+
const modalHTML = `
|
|
2786
|
+
<div class="agent-modal-overlay" id="project-agents-modal-overlay">
|
|
2787
|
+
<div class="agent-modal project-agents-modal">
|
|
2788
|
+
<div class="agent-modal-header">
|
|
2789
|
+
<div class="agent-modal-title">
|
|
2790
|
+
<div class="agent-title-main">
|
|
2791
|
+
<div class="project-icon">๐</div>
|
|
2792
|
+
<div class="agent-title-info">
|
|
2793
|
+
<h3>Project Agents</h3>
|
|
2794
|
+
<div class="agent-subtitle">
|
|
2795
|
+
<span class="project-info">${chatTitle}</span>
|
|
2796
|
+
<span class="agent-project-name">โข ${projectName}</span>
|
|
2797
|
+
</div>
|
|
2798
|
+
</div>
|
|
2799
|
+
</div>
|
|
2800
|
+
</div>
|
|
2801
|
+
<button class="agent-modal-close" id="project-agents-modal-close">×</button>
|
|
2802
|
+
</div>
|
|
2803
|
+
|
|
2804
|
+
<div class="agent-modal-content">
|
|
2805
|
+
${projectAgents.length === 0 ? `
|
|
2806
|
+
<div class="no-agents-message">
|
|
2807
|
+
<div class="no-agents-icon">๐ค</div>
|
|
2808
|
+
<h4>No project agents</h4>
|
|
2809
|
+
<p>This project doesn't have any specific agents configured.</p>
|
|
2810
|
+
<p>Create agents in your project's <code>.claude/agents/</code> directory to see them here.</p>
|
|
2811
|
+
<p><strong>Note:</strong> Global agents are available in the main agents section.</p>
|
|
2812
|
+
</div>
|
|
2813
|
+
` : `
|
|
2814
|
+
<div class="project-agents-grid">
|
|
2815
|
+
${projectAgents.map(agent => `
|
|
2816
|
+
<div class="project-agent-card" data-agent-id="${agent.name}">
|
|
2817
|
+
<div class="project-agent-header">
|
|
2818
|
+
<div class="agent-dot" style="background-color: ${agent.color}"></div>
|
|
2819
|
+
<div class="project-agent-info">
|
|
2820
|
+
<h4>${agent.name}</h4>
|
|
2821
|
+
<span class="agent-level-badge ${agent.level}">${agent.level === 'project' ? 'Project' : 'User'}</span>
|
|
2822
|
+
</div>
|
|
2823
|
+
</div>
|
|
2824
|
+
<div class="project-agent-description">
|
|
2825
|
+
${this.truncateText(agent.description, 100)}
|
|
2826
|
+
</div>
|
|
2827
|
+
<div class="project-agent-footer">
|
|
2828
|
+
<span class="project-agent-tools">${agent.tools && agent.tools.length > 0 ? `${agent.tools.length} tools` : 'All tools'}</span>
|
|
2829
|
+
<button class="project-agent-details-btn" data-agent-id="${agent.name}">Details</button>
|
|
2830
|
+
</div>
|
|
2831
|
+
</div>
|
|
2832
|
+
`).join('')}
|
|
2833
|
+
</div>
|
|
2834
|
+
|
|
2835
|
+
<div class="usage-instruction">
|
|
2836
|
+
<h4>๐ก How to use these agents</h4>
|
|
2837
|
+
<p>In your conversation, mention any agent by name:</p>
|
|
2838
|
+
<div class="usage-examples">
|
|
2839
|
+
${projectAgents.slice(0, 3).map(agent =>
|
|
2840
|
+
`<code class="usage-example">Use the ${agent.name} agent to help with this task</code>`
|
|
2841
|
+
).join('')}
|
|
2842
|
+
</div>
|
|
2843
|
+
</div>
|
|
2844
|
+
`}
|
|
2845
|
+
</div>
|
|
2846
|
+
</div>
|
|
2847
|
+
</div>
|
|
2848
|
+
`;
|
|
855
2849
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
2850
|
+
// Add modal to DOM
|
|
2851
|
+
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
|
2852
|
+
|
|
2853
|
+
// Bind close events
|
|
2854
|
+
document.getElementById('project-agents-modal-close').addEventListener('click', () => this.closeProjectAgentsModal());
|
|
2855
|
+
document.getElementById('project-agents-modal-overlay').addEventListener('click', (e) => {
|
|
2856
|
+
if (e.target.id === 'project-agents-modal-overlay') {
|
|
2857
|
+
this.closeProjectAgentsModal();
|
|
862
2858
|
}
|
|
863
2859
|
});
|
|
2860
|
+
|
|
2861
|
+
// Bind agent detail buttons
|
|
2862
|
+
const detailButtons = document.querySelectorAll('.project-agent-details-btn');
|
|
2863
|
+
detailButtons.forEach(btn => {
|
|
2864
|
+
btn.addEventListener('click', (e) => {
|
|
2865
|
+
e.stopPropagation();
|
|
2866
|
+
const agentId = btn.dataset.agentId;
|
|
2867
|
+
const agent = this.agents.find(a => a.name === agentId);
|
|
2868
|
+
if (agent) {
|
|
2869
|
+
this.closeProjectAgentsModal();
|
|
2870
|
+
this.openAgentModal(agent);
|
|
2871
|
+
}
|
|
2872
|
+
});
|
|
2873
|
+
});
|
|
2874
|
+
|
|
2875
|
+
// ESC key to close
|
|
2876
|
+
this.projectModalKeydownHandler = (e) => {
|
|
2877
|
+
if (e.key === 'Escape') {
|
|
2878
|
+
this.closeProjectAgentsModal();
|
|
2879
|
+
}
|
|
2880
|
+
};
|
|
2881
|
+
document.addEventListener('keydown', this.projectModalKeydownHandler);
|
|
864
2882
|
}
|
|
865
|
-
|
|
2883
|
+
|
|
866
2884
|
/**
|
|
867
|
-
*
|
|
868
|
-
* @param {boolean} isLoading - Whether to show loading indicator
|
|
2885
|
+
* Close project agents modal
|
|
869
2886
|
*/
|
|
870
|
-
|
|
871
|
-
const
|
|
872
|
-
if (
|
|
873
|
-
|
|
2887
|
+
closeProjectAgentsModal() {
|
|
2888
|
+
const modal = document.getElementById('project-agents-modal-overlay');
|
|
2889
|
+
if (modal) {
|
|
2890
|
+
modal.remove();
|
|
2891
|
+
}
|
|
2892
|
+
if (this.projectModalKeydownHandler) {
|
|
2893
|
+
document.removeEventListener('keydown', this.projectModalKeydownHandler);
|
|
2894
|
+
this.projectModalKeydownHandler = null;
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
/**
|
|
2899
|
+
* Refresh agents data
|
|
2900
|
+
*/
|
|
2901
|
+
async refreshAgents() {
|
|
2902
|
+
const refreshBtn = this.container.querySelector('#refresh-agents');
|
|
2903
|
+
if (refreshBtn) {
|
|
2904
|
+
refreshBtn.disabled = true;
|
|
2905
|
+
const iconElement = refreshBtn.querySelector('.btn-icon');
|
|
2906
|
+
if (iconElement) {
|
|
2907
|
+
iconElement.style.animation = 'spin 1s linear infinite';
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
try {
|
|
2912
|
+
// Just reload agents data without clearing cache
|
|
2913
|
+
await this.loadAgentsData();
|
|
2914
|
+
} catch (error) {
|
|
2915
|
+
console.error('Error refreshing agents:', error);
|
|
2916
|
+
} finally {
|
|
2917
|
+
if (refreshBtn) {
|
|
2918
|
+
refreshBtn.disabled = false;
|
|
2919
|
+
const iconElement = refreshBtn.querySelector('.btn-icon');
|
|
2920
|
+
if (iconElement) {
|
|
2921
|
+
iconElement.style.animation = '';
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
874
2924
|
}
|
|
875
2925
|
}
|
|
876
2926
|
|
|
@@ -973,6 +3023,10 @@ class AgentsPage {
|
|
|
973
3023
|
*/
|
|
974
3024
|
renderConversationsList(conversations, states, append = false) {
|
|
975
3025
|
const listContainer = this.container.querySelector('#conversations-list');
|
|
3026
|
+
if (!listContainer) {
|
|
3027
|
+
console.warn('conversations-list element not found in AgentsPage - component may not be active');
|
|
3028
|
+
return;
|
|
3029
|
+
}
|
|
976
3030
|
const filteredConversations = this.filterConversations(conversations, states);
|
|
977
3031
|
|
|
978
3032
|
// Calculate count based on filters
|
|
@@ -1005,12 +3059,21 @@ class AgentsPage {
|
|
|
1005
3059
|
const state = states[conv.id] || 'unknown';
|
|
1006
3060
|
const stateClass = this.getStateClass(state);
|
|
1007
3061
|
|
|
3062
|
+
// Check for agent usage
|
|
3063
|
+
const agentColor = this.getAgentColorForConversation(conv.id);
|
|
3064
|
+
const agentName = this.getAgentNameForConversation(conv.id);
|
|
3065
|
+
|
|
3066
|
+
// Generate title with agent indicator
|
|
3067
|
+
const titleColor = agentColor ? `style="color: ${agentColor}; border-left: 3px solid ${agentColor}; padding-left: 8px;"` : '';
|
|
3068
|
+
const agentIndicator = agentName ? `<span class="agent-indicator-small" style="background-color: ${agentColor}" title="Using ${agentName} agent">๐ค</span>` : '';
|
|
3069
|
+
|
|
1008
3070
|
return `
|
|
1009
|
-
<div class="sidebar-conversation-item" data-id="${conv.id}">
|
|
3071
|
+
<div class="sidebar-conversation-item" data-id="${conv.id}" ${agentColor ? `data-agent-color="${agentColor}"` : ''}>
|
|
1010
3072
|
<div class="sidebar-conversation-header">
|
|
1011
|
-
<div class="sidebar-conversation-title">
|
|
3073
|
+
<div class="sidebar-conversation-title" ${titleColor}>
|
|
1012
3074
|
<span class="status-dot ${stateClass}"></span>
|
|
1013
3075
|
<h4 class="sidebar-conversation-name">${conv.title || `Chat ${conv.id.slice(-8)}`}</h4>
|
|
3076
|
+
${agentIndicator}
|
|
1014
3077
|
</div>
|
|
1015
3078
|
<span class="sidebar-conversation-badge ${stateClass}">${this.getStateLabel(state)}</span>
|
|
1016
3079
|
</div>
|
|
@@ -1025,6 +3088,13 @@ class AgentsPage {
|
|
|
1025
3088
|
<div class="sidebar-conversation-preview">
|
|
1026
3089
|
<p class="sidebar-preview-text">${this.getSimpleConversationPreview(conv)}</p>
|
|
1027
3090
|
</div>
|
|
3091
|
+
|
|
3092
|
+
<div class="sidebar-conversation-actions">
|
|
3093
|
+
<button class="conversation-agents-btn" data-conversation-id="${conv.id}" title="View available agents for this project">
|
|
3094
|
+
<span class="agents-icon">๐ค</span>
|
|
3095
|
+
<span class="agents-text">Agents</span>
|
|
3096
|
+
</button>
|
|
3097
|
+
</div>
|
|
1028
3098
|
</div>
|
|
1029
3099
|
`;
|
|
1030
3100
|
}).join('');
|
|
@@ -1056,11 +3126,25 @@ class AgentsPage {
|
|
|
1056
3126
|
// Click on sidebar conversation item to select and view
|
|
1057
3127
|
const conversationItems = this.container.querySelectorAll('.sidebar-conversation-item');
|
|
1058
3128
|
conversationItems.forEach(item => {
|
|
1059
|
-
item.addEventListener('click', () => {
|
|
3129
|
+
item.addEventListener('click', (e) => {
|
|
3130
|
+
// Don't select conversation if clicking on agents button
|
|
3131
|
+
if (e.target.closest('.conversation-agents-btn')) {
|
|
3132
|
+
return;
|
|
3133
|
+
}
|
|
1060
3134
|
const conversationId = item.dataset.id;
|
|
1061
3135
|
this.selectConversation(conversationId);
|
|
1062
3136
|
});
|
|
1063
3137
|
});
|
|
3138
|
+
|
|
3139
|
+
// Bind agents button clicks
|
|
3140
|
+
const agentsButtons = this.container.querySelectorAll('.conversation-agents-btn');
|
|
3141
|
+
agentsButtons.forEach(btn => {
|
|
3142
|
+
btn.addEventListener('click', (e) => {
|
|
3143
|
+
e.stopPropagation();
|
|
3144
|
+
const conversationId = btn.dataset.conversationId;
|
|
3145
|
+
this.showProjectAgents(conversationId);
|
|
3146
|
+
});
|
|
3147
|
+
});
|
|
1064
3148
|
}
|
|
1065
3149
|
|
|
1066
3150
|
/**
|
|
@@ -1103,7 +3187,22 @@ class AgentsPage {
|
|
|
1103
3187
|
const metaElement = this.container.querySelector('#selected-conversation-meta');
|
|
1104
3188
|
|
|
1105
3189
|
if (titleElement) {
|
|
1106
|
-
|
|
3190
|
+
const baseTitle = conversation.title || `Chat ${conversation.id.slice(-8)}`;
|
|
3191
|
+
const agentName = this.getAgentNameForConversation(conversation.id);
|
|
3192
|
+
const agentColor = this.getAgentColorForConversation(conversation.id);
|
|
3193
|
+
|
|
3194
|
+
if (agentName && agentColor) {
|
|
3195
|
+
titleElement.innerHTML = `
|
|
3196
|
+
<span style="color: ${agentColor}; border-left: 3px solid ${agentColor}; padding-left: 8px;">
|
|
3197
|
+
${baseTitle}
|
|
3198
|
+
</span>
|
|
3199
|
+
<span class="agent-badge" style="background-color: ${agentColor};" title="Using ${agentName} agent">
|
|
3200
|
+
๐ค ${agentName}
|
|
3201
|
+
</span>
|
|
3202
|
+
`;
|
|
3203
|
+
} else {
|
|
3204
|
+
titleElement.textContent = baseTitle;
|
|
3205
|
+
}
|
|
1107
3206
|
}
|
|
1108
3207
|
|
|
1109
3208
|
if (metaElement) {
|
|
@@ -1305,10 +3404,29 @@ class AgentsPage {
|
|
|
1305
3404
|
// Update dot class with enhanced styling
|
|
1306
3405
|
stateDot.className = `state-dot ${stateInfo.class}`;
|
|
1307
3406
|
|
|
1308
|
-
//
|
|
3407
|
+
// Check for agent usage
|
|
3408
|
+
const agentName = this.getAgentNameForConversation(conversationId);
|
|
3409
|
+
const agentColor = this.getAgentColorForConversation(conversationId);
|
|
3410
|
+
|
|
3411
|
+
// Update text with icon, description, and agent info
|
|
3412
|
+
let stateTextContent = stateInfo.text;
|
|
3413
|
+
let stateDescriptionContent = stateInfo.description;
|
|
3414
|
+
|
|
3415
|
+
// If an agent is detected and state indicates work, update the message
|
|
3416
|
+
if (agentName && (stateInfo.class.includes('working') || stateInfo.class.includes('executing') || stateInfo.class.includes('analyzing'))) {
|
|
3417
|
+
stateTextContent = `๐ค ${agentName} agent working...`;
|
|
3418
|
+
stateDescriptionContent = `The ${agentName} agent is processing your request`;
|
|
3419
|
+
|
|
3420
|
+
// Apply agent color to the dot
|
|
3421
|
+
if (agentColor) {
|
|
3422
|
+
stateDot.style.backgroundColor = agentColor;
|
|
3423
|
+
stateDot.style.borderColor = agentColor;
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
|
|
1309
3427
|
stateText.innerHTML = `
|
|
1310
|
-
<span class="state-text-main">${
|
|
1311
|
-
<span class="state-text-description">${
|
|
3428
|
+
<span class="state-text-main">${stateTextContent}</span>
|
|
3429
|
+
<span class="state-text-description">${stateDescriptionContent}</span>
|
|
1312
3430
|
`;
|
|
1313
3431
|
|
|
1314
3432
|
// Add tooltip for additional context
|
|
@@ -1902,6 +4020,85 @@ class AgentsPage {
|
|
|
1902
4020
|
return content;
|
|
1903
4021
|
}
|
|
1904
4022
|
|
|
4023
|
+
/**
|
|
4024
|
+
* Detect which agent is being used in a conversation
|
|
4025
|
+
* @param {string} conversationId - Conversation ID
|
|
4026
|
+
* @returns {Object|null} Agent info or null if no agent detected
|
|
4027
|
+
*/
|
|
4028
|
+
detectAgentInConversation(conversationId) {
|
|
4029
|
+
const messages = this.loadedMessages.get(conversationId) || [];
|
|
4030
|
+
|
|
4031
|
+
// Look for agent indicators in recent messages
|
|
4032
|
+
for (let i = messages.length - 1; i >= Math.max(0, messages.length - 10); i--) {
|
|
4033
|
+
const message = messages[i];
|
|
4034
|
+
|
|
4035
|
+
if (message.role === 'assistant' && message.content) {
|
|
4036
|
+
let contentText = '';
|
|
4037
|
+
|
|
4038
|
+
// Extract text content from message
|
|
4039
|
+
if (Array.isArray(message.content)) {
|
|
4040
|
+
contentText = message.content
|
|
4041
|
+
.filter(block => block.type === 'text')
|
|
4042
|
+
.map(block => block.text)
|
|
4043
|
+
.join(' ');
|
|
4044
|
+
} else if (typeof message.content === 'string') {
|
|
4045
|
+
contentText = message.content;
|
|
4046
|
+
}
|
|
4047
|
+
|
|
4048
|
+
// Check for agent usage patterns
|
|
4049
|
+
const agentPatterns = [
|
|
4050
|
+
/use(?:s|d)?\s+the\s+([a-zA-Z0-9\-_]+)\s+(?:sub\s+)?agent/i,
|
|
4051
|
+
/([a-zA-Z0-9\-_]+)\s+agent\s+(?:to|for|will)/i,
|
|
4052
|
+
/delegat(?:e|ing)\s+(?:to|task|this)\s+(?:the\s+)?([a-zA-Z0-9\-_]+)\s+agent/i,
|
|
4053
|
+
/invok(?:e|ing)\s+(?:the\s+)?([a-zA-Z0-9\-_]+)\s+agent/i
|
|
4054
|
+
];
|
|
4055
|
+
|
|
4056
|
+
for (const pattern of agentPatterns) {
|
|
4057
|
+
const match = contentText.match(pattern);
|
|
4058
|
+
if (match) {
|
|
4059
|
+
const detectedAgentName = match[1].toLowerCase();
|
|
4060
|
+
|
|
4061
|
+
// Find matching agent from our loaded agents
|
|
4062
|
+
const agent = this.agents.find(a =>
|
|
4063
|
+
a.name.toLowerCase() === detectedAgentName ||
|
|
4064
|
+
a.name.toLowerCase().replace(/-/g, '') === detectedAgentName.replace(/-/g, '')
|
|
4065
|
+
);
|
|
4066
|
+
|
|
4067
|
+
if (agent) {
|
|
4068
|
+
return {
|
|
4069
|
+
agent,
|
|
4070
|
+
detectedAt: message.timestamp,
|
|
4071
|
+
confidence: 'high'
|
|
4072
|
+
};
|
|
4073
|
+
}
|
|
4074
|
+
}
|
|
4075
|
+
}
|
|
4076
|
+
}
|
|
4077
|
+
}
|
|
4078
|
+
|
|
4079
|
+
return null;
|
|
4080
|
+
}
|
|
4081
|
+
|
|
4082
|
+
/**
|
|
4083
|
+
* Get agent color for conversation
|
|
4084
|
+
* @param {string} conversationId - Conversation ID
|
|
4085
|
+
* @returns {string|null} Agent color or null
|
|
4086
|
+
*/
|
|
4087
|
+
getAgentColorForConversation(conversationId) {
|
|
4088
|
+
const agentInfo = this.detectAgentInConversation(conversationId);
|
|
4089
|
+
return agentInfo ? agentInfo.agent.color : null;
|
|
4090
|
+
}
|
|
4091
|
+
|
|
4092
|
+
/**
|
|
4093
|
+
* Get agent name for conversation
|
|
4094
|
+
* @param {string} conversationId - Conversation ID
|
|
4095
|
+
* @returns {string|null} Agent name or null
|
|
4096
|
+
*/
|
|
4097
|
+
getAgentNameForConversation(conversationId) {
|
|
4098
|
+
const agentInfo = this.detectAgentInConversation(conversationId);
|
|
4099
|
+
return agentInfo ? agentInfo.agent.name : null;
|
|
4100
|
+
}
|
|
4101
|
+
|
|
1905
4102
|
/**
|
|
1906
4103
|
* Format relative time
|
|
1907
4104
|
* @param {Date} date - Date to format
|
|
@@ -2454,6 +4651,18 @@ class AgentsPage {
|
|
|
2454
4651
|
* @param {Object} activeStates - Active conversation states (direct object, not nested)
|
|
2455
4652
|
*/
|
|
2456
4653
|
updateConversationStates(activeStates) {
|
|
4654
|
+
if (!this.isInitialized) {
|
|
4655
|
+
console.warn('AgentsPage: updateConversationStates called before initialization');
|
|
4656
|
+
return;
|
|
4657
|
+
}
|
|
4658
|
+
|
|
4659
|
+
// Check if we're still on the agents page by verifying our key element exists
|
|
4660
|
+
const conversationsContainer = this.container.querySelector('#conversations-list');
|
|
4661
|
+
if (!conversationsContainer) {
|
|
4662
|
+
console.log('AgentsPage: Not on agents page, skipping conversation states update');
|
|
4663
|
+
return;
|
|
4664
|
+
}
|
|
4665
|
+
|
|
2457
4666
|
const conversations = this.stateService.getStateProperty('conversations') || [];
|
|
2458
4667
|
|
|
2459
4668
|
|