agentgui 1.0.92 → 1.0.93
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/database.js +7 -5
- package/package.json +1 -1
- package/server.js +6 -4
- package/static/index.html +192 -0
- package/static/js/client.js +17 -10
- package/static/js/conversations.js +165 -9
package/database.js
CHANGED
|
@@ -197,7 +197,8 @@ try {
|
|
|
197
197
|
projectPath: 'TEXT',
|
|
198
198
|
gitBranch: 'TEXT',
|
|
199
199
|
sourcePath: 'TEXT',
|
|
200
|
-
lastSyncedAt: 'INTEGER'
|
|
200
|
+
lastSyncedAt: 'INTEGER',
|
|
201
|
+
workingDirectory: 'TEXT'
|
|
201
202
|
};
|
|
202
203
|
|
|
203
204
|
let addedColumns = false;
|
|
@@ -228,18 +229,19 @@ function generateId(prefix) {
|
|
|
228
229
|
}
|
|
229
230
|
|
|
230
231
|
export const queries = {
|
|
231
|
-
createConversation(agentId, title = null) {
|
|
232
|
+
createConversation(agentId, title = null, workingDirectory = null) {
|
|
232
233
|
const id = generateId('conv');
|
|
233
234
|
const now = Date.now();
|
|
234
235
|
const stmt = db.prepare(
|
|
235
|
-
`INSERT INTO conversations (id, agentId, title, created_at, updated_at, status) VALUES (?, ?, ?, ?, ?, ?)`
|
|
236
|
+
`INSERT INTO conversations (id, agentId, title, created_at, updated_at, status, workingDirectory) VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
236
237
|
);
|
|
237
|
-
stmt.run(id, agentId, title, now, now, 'active');
|
|
238
|
+
stmt.run(id, agentId, title, now, now, 'active', workingDirectory);
|
|
238
239
|
|
|
239
240
|
return {
|
|
240
241
|
id,
|
|
241
242
|
agentId,
|
|
242
243
|
title,
|
|
244
|
+
workingDirectory,
|
|
243
245
|
created_at: now,
|
|
244
246
|
updated_at: now,
|
|
245
247
|
status: 'active'
|
|
@@ -258,7 +260,7 @@ export const queries = {
|
|
|
258
260
|
|
|
259
261
|
getConversationsList() {
|
|
260
262
|
const stmt = db.prepare(
|
|
261
|
-
'SELECT id, title, agentType, created_at, updated_at, messageCount FROM conversations WHERE status != ? ORDER BY updated_at DESC'
|
|
263
|
+
'SELECT id, title, agentType, created_at, updated_at, messageCount, workingDirectory FROM conversations WHERE status != ? ORDER BY updated_at DESC'
|
|
262
264
|
);
|
|
263
265
|
return stmt.all('deleted');
|
|
264
266
|
},
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -79,8 +79,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
79
79
|
|
|
80
80
|
if (pathOnly === '/api/conversations' && req.method === 'POST') {
|
|
81
81
|
const body = await parseBody(req);
|
|
82
|
-
const conversation = queries.createConversation(body.agentId, body.title);
|
|
83
|
-
queries.createEvent('conversation.created', { agentId: body.agentId }, conversation.id);
|
|
82
|
+
const conversation = queries.createConversation(body.agentId, body.title, body.workingDirectory || null);
|
|
83
|
+
queries.createEvent('conversation.created', { agentId: body.agentId, workingDirectory: conversation.workingDirectory }, conversation.id);
|
|
84
84
|
broadcastSync({ type: 'conversation_created', conversation });
|
|
85
85
|
res.writeHead(201, { 'Content-Type': 'application/json' });
|
|
86
86
|
res.end(JSON.stringify({ conversation }));
|
|
@@ -377,7 +377,8 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
|
|
|
377
377
|
try {
|
|
378
378
|
debugLog(`[stream] Starting: conversationId=${conversationId}, sessionId=${sessionId}, agentId=${agentId}, skipPermissions=${skipPermissions}`);
|
|
379
379
|
|
|
380
|
-
const
|
|
380
|
+
const conv = queries.getConversation(conversationId);
|
|
381
|
+
const cwd = conv?.workingDirectory || '/config';
|
|
381
382
|
const actualAgentId = agentId || 'claude-code';
|
|
382
383
|
|
|
383
384
|
debugLog(`[stream] Calling runClaudeWithStreaming with config: skipPermissions=${skipPermissions}`);
|
|
@@ -505,7 +506,8 @@ async function processMessage(conversationId, messageId, content, agentId) {
|
|
|
505
506
|
try {
|
|
506
507
|
debugLog(`[processMessage] Starting: conversationId=${conversationId}, agentId=${agentId}`);
|
|
507
508
|
|
|
508
|
-
const
|
|
509
|
+
const conv = queries.getConversation(conversationId);
|
|
510
|
+
const cwd = conv?.workingDirectory || '/config';
|
|
509
511
|
const actualAgentId = agentId || 'claude-code';
|
|
510
512
|
|
|
511
513
|
// Handle both string content and object content (for structured messages)
|
package/static/index.html
CHANGED
|
@@ -552,6 +552,180 @@
|
|
|
552
552
|
}
|
|
553
553
|
}
|
|
554
554
|
|
|
555
|
+
/* Folder Browser Modal */
|
|
556
|
+
.folder-modal-overlay {
|
|
557
|
+
display: none;
|
|
558
|
+
position: fixed;
|
|
559
|
+
top: 0;
|
|
560
|
+
left: 0;
|
|
561
|
+
width: 100%;
|
|
562
|
+
height: 100%;
|
|
563
|
+
background: rgba(0, 0, 0, 0.5);
|
|
564
|
+
z-index: 2000;
|
|
565
|
+
align-items: center;
|
|
566
|
+
justify-content: center;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.folder-modal-overlay.visible {
|
|
570
|
+
display: flex;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
.folder-modal {
|
|
574
|
+
background: var(--color-bg-primary);
|
|
575
|
+
border: 1px solid var(--color-border);
|
|
576
|
+
border-radius: 0.5rem;
|
|
577
|
+
width: 500px;
|
|
578
|
+
max-width: 90vw;
|
|
579
|
+
max-height: 80vh;
|
|
580
|
+
display: flex;
|
|
581
|
+
flex-direction: column;
|
|
582
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
.folder-modal-header {
|
|
586
|
+
padding: 1rem;
|
|
587
|
+
border-bottom: 1px solid var(--color-border);
|
|
588
|
+
display: flex;
|
|
589
|
+
justify-content: space-between;
|
|
590
|
+
align-items: center;
|
|
591
|
+
flex-shrink: 0;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
.folder-modal-header h3 {
|
|
595
|
+
margin: 0;
|
|
596
|
+
font-size: 1rem;
|
|
597
|
+
font-weight: 600;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
.folder-modal-close {
|
|
601
|
+
background: none;
|
|
602
|
+
border: none;
|
|
603
|
+
font-size: 1.25rem;
|
|
604
|
+
cursor: pointer;
|
|
605
|
+
color: var(--color-text-secondary);
|
|
606
|
+
padding: 0.25rem;
|
|
607
|
+
line-height: 1;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
.folder-modal-close:hover {
|
|
611
|
+
color: var(--color-text-primary);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
.folder-breadcrumb {
|
|
615
|
+
padding: 0.75rem 1rem;
|
|
616
|
+
border-bottom: 1px solid var(--color-border);
|
|
617
|
+
font-size: 0.8rem;
|
|
618
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
619
|
+
color: var(--color-text-secondary);
|
|
620
|
+
background: var(--color-bg-secondary);
|
|
621
|
+
display: flex;
|
|
622
|
+
align-items: center;
|
|
623
|
+
gap: 0.25rem;
|
|
624
|
+
flex-shrink: 0;
|
|
625
|
+
overflow-x: auto;
|
|
626
|
+
white-space: nowrap;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
.folder-breadcrumb-segment {
|
|
630
|
+
cursor: pointer;
|
|
631
|
+
color: var(--color-primary);
|
|
632
|
+
padding: 0.125rem 0.25rem;
|
|
633
|
+
border-radius: 0.25rem;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
.folder-breadcrumb-segment:hover {
|
|
637
|
+
background: var(--color-bg-primary);
|
|
638
|
+
text-decoration: underline;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
.folder-breadcrumb-separator {
|
|
642
|
+
color: var(--color-text-secondary);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
.folder-list {
|
|
646
|
+
flex: 1;
|
|
647
|
+
overflow-y: auto;
|
|
648
|
+
min-height: 200px;
|
|
649
|
+
max-height: 400px;
|
|
650
|
+
list-style: none;
|
|
651
|
+
margin: 0;
|
|
652
|
+
padding: 0;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
.folder-list-item {
|
|
656
|
+
padding: 0.5rem 1rem;
|
|
657
|
+
cursor: pointer;
|
|
658
|
+
display: flex;
|
|
659
|
+
align-items: center;
|
|
660
|
+
gap: 0.5rem;
|
|
661
|
+
font-size: 0.875rem;
|
|
662
|
+
border-bottom: 1px solid var(--color-border);
|
|
663
|
+
transition: background-color 0.15s;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
.folder-list-item:hover {
|
|
667
|
+
background: var(--color-bg-secondary);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
.folder-list-item-icon {
|
|
671
|
+
font-size: 1rem;
|
|
672
|
+
flex-shrink: 0;
|
|
673
|
+
width: 1.25rem;
|
|
674
|
+
text-align: center;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
.folder-list-item-name {
|
|
678
|
+
flex: 1;
|
|
679
|
+
overflow: hidden;
|
|
680
|
+
text-overflow: ellipsis;
|
|
681
|
+
white-space: nowrap;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
.folder-list-empty {
|
|
685
|
+
padding: 2rem 1rem;
|
|
686
|
+
text-align: center;
|
|
687
|
+
color: var(--color-text-secondary);
|
|
688
|
+
font-size: 0.875rem;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
.folder-list-loading {
|
|
692
|
+
padding: 2rem 1rem;
|
|
693
|
+
text-align: center;
|
|
694
|
+
color: var(--color-text-secondary);
|
|
695
|
+
font-size: 0.875rem;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
.folder-list-error {
|
|
699
|
+
padding: 1rem;
|
|
700
|
+
text-align: center;
|
|
701
|
+
color: var(--color-error);
|
|
702
|
+
font-size: 0.875rem;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
.folder-modal-footer {
|
|
706
|
+
padding: 0.75rem 1rem;
|
|
707
|
+
border-top: 1px solid var(--color-border);
|
|
708
|
+
display: flex;
|
|
709
|
+
justify-content: flex-end;
|
|
710
|
+
gap: 0.5rem;
|
|
711
|
+
flex-shrink: 0;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
.folder-modal-footer .btn {
|
|
715
|
+
padding: 0.5rem 1rem;
|
|
716
|
+
font-size: 0.8rem;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
.btn-secondary {
|
|
720
|
+
background: var(--color-bg-secondary);
|
|
721
|
+
color: var(--color-text-primary);
|
|
722
|
+
border: 1px solid var(--color-border);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
.btn-secondary:hover {
|
|
726
|
+
background: var(--color-border);
|
|
727
|
+
}
|
|
728
|
+
|
|
555
729
|
/* Utilities */
|
|
556
730
|
.flex {
|
|
557
731
|
display: flex;
|
|
@@ -849,6 +1023,24 @@
|
|
|
849
1023
|
</div>
|
|
850
1024
|
</div>
|
|
851
1025
|
|
|
1026
|
+
<!-- Folder Browser Modal -->
|
|
1027
|
+
<div id="folderBrowserModal" class="folder-modal-overlay">
|
|
1028
|
+
<div class="folder-modal">
|
|
1029
|
+
<div class="folder-modal-header">
|
|
1030
|
+
<h3>Select Working Directory</h3>
|
|
1031
|
+
<button class="folder-modal-close" data-folder-close>×</button>
|
|
1032
|
+
</div>
|
|
1033
|
+
<div id="folderBreadcrumb" class="folder-breadcrumb"></div>
|
|
1034
|
+
<ul id="folderList" class="folder-list">
|
|
1035
|
+
<li class="folder-list-loading">Loading...</li>
|
|
1036
|
+
</ul>
|
|
1037
|
+
<div class="folder-modal-footer">
|
|
1038
|
+
<button class="btn btn-secondary" data-folder-cancel>Cancel</button>
|
|
1039
|
+
<button class="btn btn-primary" data-folder-select>Select This Folder</button>
|
|
1040
|
+
</div>
|
|
1041
|
+
</div>
|
|
1042
|
+
</div>
|
|
1043
|
+
|
|
852
1044
|
<!-- Scripts - Order matters! -->
|
|
853
1045
|
<!-- 1. Event Processor (no dependencies) -->
|
|
854
1046
|
<script src="/gm/js/event-processor.js"></script>
|
package/static/js/client.js
CHANGED
|
@@ -168,8 +168,10 @@ class AgentGUIClient {
|
|
|
168
168
|
themeToggle.addEventListener('click', () => this.toggleTheme());
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
window.addEventListener('create-new-conversation', (event) => {
|
|
172
|
+
const detail = event.detail || {};
|
|
173
|
+
this.createNewConversation(detail.workingDirectory, detail.title);
|
|
174
|
+
});
|
|
173
175
|
|
|
174
176
|
// Listen for conversation selection
|
|
175
177
|
window.addEventListener('conversation-selected', (event) => {
|
|
@@ -636,16 +638,17 @@ class AgentGUIClient {
|
|
|
636
638
|
/**
|
|
637
639
|
* Create a new empty conversation
|
|
638
640
|
*/
|
|
639
|
-
async createNewConversation() {
|
|
641
|
+
async createNewConversation(workingDirectory, title) {
|
|
640
642
|
try {
|
|
641
643
|
const agentId = this.ui.agentSelector?.value || 'claude-code';
|
|
644
|
+
const convTitle = title || 'New Conversation';
|
|
645
|
+
const body = { agentId, title: convTitle };
|
|
646
|
+
if (workingDirectory) body.workingDirectory = workingDirectory;
|
|
647
|
+
|
|
642
648
|
const response = await fetch(window.__BASE_URL + '/api/conversations', {
|
|
643
649
|
method: 'POST',
|
|
644
650
|
headers: { 'Content-Type': 'application/json' },
|
|
645
|
-
body: JSON.stringify(
|
|
646
|
-
agentId,
|
|
647
|
-
title: 'New Conversation'
|
|
648
|
-
})
|
|
651
|
+
body: JSON.stringify(body)
|
|
649
652
|
});
|
|
650
653
|
|
|
651
654
|
if (!response.ok) {
|
|
@@ -655,10 +658,13 @@ class AgentGUIClient {
|
|
|
655
658
|
const { conversation } = await response.json();
|
|
656
659
|
this.state.currentConversation = conversation;
|
|
657
660
|
|
|
658
|
-
// Refresh conversation list
|
|
659
661
|
await this.loadConversations();
|
|
660
662
|
|
|
661
|
-
|
|
663
|
+
if (window.conversationManager) {
|
|
664
|
+
window.conversationManager.loadConversations();
|
|
665
|
+
window.conversationManager.select(conversation.id);
|
|
666
|
+
}
|
|
667
|
+
|
|
662
668
|
if (this.ui.messageInput) {
|
|
663
669
|
this.ui.messageInput.value = '';
|
|
664
670
|
this.ui.messageInput.focus();
|
|
@@ -690,10 +696,11 @@ class AgentGUIClient {
|
|
|
690
696
|
// Clear output and display conversation header
|
|
691
697
|
const outputEl = document.getElementById('output');
|
|
692
698
|
if (outputEl) {
|
|
699
|
+
const wdInfo = conversation.workingDirectory ? ` • ${this.escapeHtml(conversation.workingDirectory)}` : '';
|
|
693
700
|
outputEl.innerHTML = `
|
|
694
701
|
<div class="conversation-header">
|
|
695
702
|
<h2>${this.escapeHtml(conversation.title || 'Conversation')}</h2>
|
|
696
|
-
<p class="text-secondary">${conversation.agentType || 'unknown'} • ${new Date(conversation.created_at).toLocaleDateString()}</p>
|
|
703
|
+
<p class="text-secondary">${conversation.agentType || 'unknown'} • ${new Date(conversation.created_at).toLocaleDateString()}${wdInfo}</p>
|
|
697
704
|
</div>
|
|
698
705
|
<div class="conversation-messages">
|
|
699
706
|
${this.renderMessages(messagesData.messages || [])}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Conversations Module
|
|
3
3
|
* Manages conversation list sidebar with real-time updates
|
|
4
|
+
* Includes folder browser for selecting working directory on new conversation
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
class ConversationManager {
|
|
@@ -12,23 +13,180 @@ class ConversationManager {
|
|
|
12
13
|
this.newBtn = document.querySelector('[data-new-conversation]');
|
|
13
14
|
this.sidebarEl = document.querySelector('[data-sidebar]');
|
|
14
15
|
|
|
16
|
+
this.folderBrowser = {
|
|
17
|
+
modal: null,
|
|
18
|
+
listEl: null,
|
|
19
|
+
breadcrumbEl: null,
|
|
20
|
+
currentPath: '~',
|
|
21
|
+
homePath: '~'
|
|
22
|
+
};
|
|
23
|
+
|
|
15
24
|
if (!this.listEl) return;
|
|
16
25
|
|
|
17
26
|
this.init();
|
|
18
27
|
}
|
|
19
28
|
|
|
20
29
|
async init() {
|
|
21
|
-
this.newBtn?.addEventListener('click', () => this.
|
|
30
|
+
this.newBtn?.addEventListener('click', () => this.openFolderBrowser());
|
|
22
31
|
this.loadConversations();
|
|
23
32
|
this.setupWebSocketListener();
|
|
33
|
+
this.setupFolderBrowser();
|
|
24
34
|
|
|
25
|
-
// Auto-refresh every 30 seconds
|
|
26
35
|
setInterval(() => this.loadConversations(), 30000);
|
|
27
36
|
}
|
|
28
37
|
|
|
38
|
+
setupFolderBrowser() {
|
|
39
|
+
this.folderBrowser.modal = document.getElementById('folderBrowserModal');
|
|
40
|
+
this.folderBrowser.listEl = document.getElementById('folderList');
|
|
41
|
+
this.folderBrowser.breadcrumbEl = document.getElementById('folderBreadcrumb');
|
|
42
|
+
|
|
43
|
+
if (!this.folderBrowser.modal) return;
|
|
44
|
+
|
|
45
|
+
const closeBtn = this.folderBrowser.modal.querySelector('[data-folder-close]');
|
|
46
|
+
const cancelBtn = this.folderBrowser.modal.querySelector('[data-folder-cancel]');
|
|
47
|
+
const selectBtn = this.folderBrowser.modal.querySelector('[data-folder-select]');
|
|
48
|
+
|
|
49
|
+
closeBtn?.addEventListener('click', () => this.closeFolderBrowser());
|
|
50
|
+
cancelBtn?.addEventListener('click', () => this.closeFolderBrowser());
|
|
51
|
+
selectBtn?.addEventListener('click', () => this.confirmFolderSelection());
|
|
52
|
+
|
|
53
|
+
this.folderBrowser.modal.addEventListener('click', (e) => {
|
|
54
|
+
if (e.target === this.folderBrowser.modal) this.closeFolderBrowser();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
this.fetchHomePath();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async fetchHomePath() {
|
|
61
|
+
try {
|
|
62
|
+
const res = await fetch((window.__BASE_URL || '') + '/api/home');
|
|
63
|
+
if (res.ok) {
|
|
64
|
+
const data = await res.json();
|
|
65
|
+
this.folderBrowser.homePath = data.home || '~';
|
|
66
|
+
}
|
|
67
|
+
} catch (e) {
|
|
68
|
+
console.error('Failed to fetch home path:', e);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
openFolderBrowser() {
|
|
73
|
+
if (!this.folderBrowser.modal) {
|
|
74
|
+
this.createNew();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
this.folderBrowser.currentPath = '~';
|
|
78
|
+
this.folderBrowser.modal.classList.add('visible');
|
|
79
|
+
this.loadFolders('~');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
closeFolderBrowser() {
|
|
83
|
+
this.folderBrowser.modal?.classList.remove('visible');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async loadFolders(dirPath) {
|
|
87
|
+
this.folderBrowser.currentPath = dirPath;
|
|
88
|
+
this.renderBreadcrumb(dirPath);
|
|
89
|
+
|
|
90
|
+
if (!this.folderBrowser.listEl) return;
|
|
91
|
+
this.folderBrowser.listEl.innerHTML = '<li class="folder-list-loading">Loading...</li>';
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const res = await fetch((window.__BASE_URL || '') + '/api/folders', {
|
|
95
|
+
method: 'POST',
|
|
96
|
+
headers: { 'Content-Type': 'application/json' },
|
|
97
|
+
body: JSON.stringify({ path: dirPath })
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (!res.ok) {
|
|
101
|
+
const errData = await res.json().catch(() => ({}));
|
|
102
|
+
throw new Error(errData.error || `HTTP ${res.status}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const data = await res.json();
|
|
106
|
+
const folders = data.folders || [];
|
|
107
|
+
|
|
108
|
+
this.folderBrowser.listEl.innerHTML = '';
|
|
109
|
+
|
|
110
|
+
if (dirPath !== '~' && dirPath !== '/' && dirPath !== this.folderBrowser.homePath) {
|
|
111
|
+
const parentPath = this.getParentPath(dirPath);
|
|
112
|
+
const upItem = document.createElement('li');
|
|
113
|
+
upItem.className = 'folder-list-item';
|
|
114
|
+
upItem.innerHTML = '<span class="folder-list-item-icon">..</span><span class="folder-list-item-name">Parent Directory</span>';
|
|
115
|
+
upItem.addEventListener('click', () => this.loadFolders(parentPath));
|
|
116
|
+
this.folderBrowser.listEl.appendChild(upItem);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (folders.length === 0 && this.folderBrowser.listEl.children.length === 0) {
|
|
120
|
+
this.folderBrowser.listEl.innerHTML = '<li class="folder-list-empty">No subdirectories</li>';
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for (const folder of folders) {
|
|
125
|
+
const li = document.createElement('li');
|
|
126
|
+
li.className = 'folder-list-item';
|
|
127
|
+
li.innerHTML = `<span class="folder-list-item-icon">📁</span><span class="folder-list-item-name">${this.escapeHtml(folder.name)}</span>`;
|
|
128
|
+
li.addEventListener('click', () => {
|
|
129
|
+
const expandedBase = dirPath === '~' ? this.folderBrowser.homePath : dirPath;
|
|
130
|
+
const newPath = expandedBase + '/' + folder.name;
|
|
131
|
+
this.loadFolders(newPath);
|
|
132
|
+
});
|
|
133
|
+
this.folderBrowser.listEl.appendChild(li);
|
|
134
|
+
}
|
|
135
|
+
} catch (err) {
|
|
136
|
+
console.error('Failed to load folders:', err);
|
|
137
|
+
this.folderBrowser.listEl.innerHTML = `<li class="folder-list-error">Error: ${this.escapeHtml(err.message)}</li>`;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
getParentPath(dirPath) {
|
|
142
|
+
const expanded = dirPath === '~' ? this.folderBrowser.homePath : dirPath;
|
|
143
|
+
const parts = expanded.split('/').filter(Boolean);
|
|
144
|
+
if (parts.length <= 1) return '/';
|
|
145
|
+
parts.pop();
|
|
146
|
+
return '/' + parts.join('/');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
renderBreadcrumb(dirPath) {
|
|
150
|
+
if (!this.folderBrowser.breadcrumbEl) return;
|
|
151
|
+
|
|
152
|
+
const expanded = dirPath === '~' ? this.folderBrowser.homePath : dirPath;
|
|
153
|
+
const parts = expanded.split('/').filter(Boolean);
|
|
154
|
+
|
|
155
|
+
let html = '';
|
|
156
|
+
html += '<span class="folder-breadcrumb-segment" data-path="/">/ </span>';
|
|
157
|
+
|
|
158
|
+
let accumulated = '';
|
|
159
|
+
for (let i = 0; i < parts.length; i++) {
|
|
160
|
+
accumulated += '/' + parts[i];
|
|
161
|
+
const isLast = i === parts.length - 1;
|
|
162
|
+
html += '<span class="folder-breadcrumb-separator">/</span>';
|
|
163
|
+
html += `<span class="folder-breadcrumb-segment${isLast ? '' : ''}" data-path="${this.escapeHtml(accumulated)}">${this.escapeHtml(parts[i])}</span>`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this.folderBrowser.breadcrumbEl.innerHTML = html;
|
|
167
|
+
|
|
168
|
+
this.folderBrowser.breadcrumbEl.querySelectorAll('.folder-breadcrumb-segment').forEach(seg => {
|
|
169
|
+
seg.addEventListener('click', () => {
|
|
170
|
+
const p = seg.dataset.path;
|
|
171
|
+
if (p) this.loadFolders(p);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
confirmFolderSelection() {
|
|
177
|
+
const currentPath = this.folderBrowser.currentPath;
|
|
178
|
+
const expanded = currentPath === '~' ? this.folderBrowser.homePath : currentPath;
|
|
179
|
+
this.closeFolderBrowser();
|
|
180
|
+
|
|
181
|
+
const dirName = expanded.split('/').filter(Boolean).pop() || 'root';
|
|
182
|
+
window.dispatchEvent(new CustomEvent('create-new-conversation', {
|
|
183
|
+
detail: { workingDirectory: expanded, title: dirName }
|
|
184
|
+
}));
|
|
185
|
+
}
|
|
186
|
+
|
|
29
187
|
async loadConversations() {
|
|
30
188
|
try {
|
|
31
|
-
const res = await fetch('/
|
|
189
|
+
const res = await fetch((window.__BASE_URL || '') + '/api/conversations');
|
|
32
190
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
33
191
|
|
|
34
192
|
const data = await res.json();
|
|
@@ -51,7 +209,6 @@ class ConversationManager {
|
|
|
51
209
|
this.listEl.innerHTML = '';
|
|
52
210
|
this.emptyEl.style.display = 'none';
|
|
53
211
|
|
|
54
|
-
// Sort by most recent first
|
|
55
212
|
const sorted = [...this.conversations].sort((a, b) =>
|
|
56
213
|
new Date(b.createdAt || 0) - new Date(a.createdAt || 0)
|
|
57
214
|
);
|
|
@@ -71,10 +228,13 @@ class ConversationManager {
|
|
|
71
228
|
const title = conv.title || `Conversation ${conv.id.slice(0, 8)}`;
|
|
72
229
|
const timestamp = conv.created_at ? new Date(conv.created_at).toLocaleDateString() : 'Unknown';
|
|
73
230
|
const agent = conv.agentType || 'unknown';
|
|
231
|
+
const wd = conv.workingDirectory ? conv.workingDirectory.split('/').pop() : '';
|
|
232
|
+
const metaParts = [agent, timestamp];
|
|
233
|
+
if (wd) metaParts.push(wd);
|
|
74
234
|
|
|
75
235
|
li.innerHTML = `
|
|
76
236
|
<div class="conversation-item-title">${this.escapeHtml(title)}</div>
|
|
77
|
-
<div class="conversation-item-meta">${
|
|
237
|
+
<div class="conversation-item-meta">${metaParts.join(' \u2022 ')}</div>
|
|
78
238
|
`;
|
|
79
239
|
|
|
80
240
|
li.addEventListener('click', () => this.select(conv.id));
|
|
@@ -84,7 +244,6 @@ class ConversationManager {
|
|
|
84
244
|
select(convId) {
|
|
85
245
|
this.activeId = convId;
|
|
86
246
|
|
|
87
|
-
// Update active indicator
|
|
88
247
|
document.querySelectorAll('.conversation-item').forEach(item => {
|
|
89
248
|
item.classList.remove('active');
|
|
90
249
|
});
|
|
@@ -92,7 +251,6 @@ class ConversationManager {
|
|
|
92
251
|
const active = document.querySelector(`[data-conv-id="${convId}"]`);
|
|
93
252
|
if (active) active.classList.add('active');
|
|
94
253
|
|
|
95
|
-
// Emit event for client.js to handle
|
|
96
254
|
window.dispatchEvent(new CustomEvent('conversation-selected', {
|
|
97
255
|
detail: { conversationId: convId }
|
|
98
256
|
}));
|
|
@@ -110,7 +268,6 @@ class ConversationManager {
|
|
|
110
268
|
}
|
|
111
269
|
|
|
112
270
|
addConversation(conv) {
|
|
113
|
-
// Add to beginning (most recent)
|
|
114
271
|
this.conversations.unshift(conv);
|
|
115
272
|
this.render();
|
|
116
273
|
}
|
|
@@ -149,7 +306,6 @@ class ConversationManager {
|
|
|
149
306
|
}
|
|
150
307
|
}
|
|
151
308
|
|
|
152
|
-
// Initialize when DOM is ready
|
|
153
309
|
if (document.readyState === 'loading') {
|
|
154
310
|
document.addEventListener('DOMContentLoaded', () => {
|
|
155
311
|
window.conversationManager = new ConversationManager();
|