@hapico/cli 0.0.29 → 0.0.30
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/bin/index.js +1 -1
- package/bin/tools/vibe/index.js +9 -95
- package/dist/index.js +1 -1
- package/dist/tools/vibe/index.js +9 -95
- package/index.ts +1 -1
- package/package.json +1 -1
- package/tools/vibe/index.ts +9 -95
package/bin/index.js
CHANGED
|
@@ -411,7 +411,7 @@ class RoomState {
|
|
|
411
411
|
return this.isConnected;
|
|
412
412
|
}
|
|
413
413
|
}
|
|
414
|
-
commander_1.program.version("0.0.
|
|
414
|
+
commander_1.program.version("0.0.30").description("Hapico CLI for project management");
|
|
415
415
|
commander_1.program
|
|
416
416
|
.command("clone <id>")
|
|
417
417
|
.description("Clone a project by ID")
|
package/bin/tools/vibe/index.js
CHANGED
|
@@ -17,7 +17,8 @@ const scanRecursively = async (dir, baseDir) => {
|
|
|
17
17
|
const list = await promises_1.default.readdir(dir, { withFileTypes: true });
|
|
18
18
|
for (const entry of list) {
|
|
19
19
|
const fullPath = node_path_1.default.join(dir, entry.name);
|
|
20
|
-
|
|
20
|
+
// Chuẩn hóa đường dẫn về forward slash để nhất quán giữa các hệ điều hành
|
|
21
|
+
const relativePath = node_path_1.default.relative(baseDir, fullPath).split(node_path_1.default.sep).join('/');
|
|
21
22
|
if (shouldIgnorePath(relativePath))
|
|
22
23
|
continue;
|
|
23
24
|
if (entry.isDirectory()) {
|
|
@@ -604,7 +605,8 @@ const startServer = async (rootDir) => {
|
|
|
604
605
|
|
|
605
606
|
async init() {
|
|
606
607
|
try {
|
|
607
|
-
|
|
608
|
+
// Sử dụng relative URL để tránh lỗi phân giải localhost/127.0.0.1 trên Windows
|
|
609
|
+
const res = await fetch('/api/files');
|
|
608
610
|
const data = await res.json();
|
|
609
611
|
this.rawFiles = data;
|
|
610
612
|
this.buildTree();
|
|
@@ -614,98 +616,10 @@ const startServer = async (rootDir) => {
|
|
|
614
616
|
this.loading = false;
|
|
615
617
|
}
|
|
616
618
|
|
|
617
|
-
// Search watcher
|
|
618
619
|
this.$watch('search', () => this.buildTree());
|
|
619
|
-
|
|
620
|
-
// Auto-generate prompt
|
|
621
620
|
this.$watch('instruction', () => this.debouncedFetchPrompt());
|
|
622
621
|
this.$watch('selectedFiles', () => this.debouncedFetchPrompt());
|
|
623
622
|
},
|
|
624
|
-
|
|
625
|
-
// ===========================
|
|
626
|
-
// Tree Logic
|
|
627
|
-
// ===========================
|
|
628
|
-
|
|
629
|
-
buildTree() {
|
|
630
|
-
// 1. Convert paths to object structure
|
|
631
|
-
const root = {};
|
|
632
|
-
const searchLower = this.search.toLowerCase();
|
|
633
|
-
|
|
634
|
-
this.rawFiles.forEach(file => {
|
|
635
|
-
if (this.search && !file.path.toLowerCase().includes(searchLower)) return;
|
|
636
|
-
|
|
637
|
-
const parts = file.path.split(/[\\\\/]/);
|
|
638
|
-
let current = root;
|
|
639
|
-
|
|
640
|
-
parts.forEach((part, index) => {
|
|
641
|
-
if (!current[part]) {
|
|
642
|
-
current[part] = {
|
|
643
|
-
name: part,
|
|
644
|
-
path: parts.slice(0, index + 1).join('/'),
|
|
645
|
-
children: {},
|
|
646
|
-
type: index === parts.length - 1 ? 'file' : 'folder',
|
|
647
|
-
fullPath: file.path // Only meaningful for file
|
|
648
|
-
};
|
|
649
|
-
}
|
|
650
|
-
current = current[part].children;
|
|
651
|
-
});
|
|
652
|
-
});
|
|
653
|
-
|
|
654
|
-
// 2. Flatten object structure into renderable list with depth
|
|
655
|
-
this.treeNodes = [];
|
|
656
|
-
const traverse = (nodeMap, depth) => {
|
|
657
|
-
// Sort: Folders first, then Files. Alphabetical.
|
|
658
|
-
const keys = Object.keys(nodeMap).sort((a, b) => {
|
|
659
|
-
const nodeA = nodeMap[a];
|
|
660
|
-
const nodeB = nodeMap[b];
|
|
661
|
-
if (nodeA.type !== nodeB.type) return nodeA.type === 'folder' ? -1 : 1;
|
|
662
|
-
return a.localeCompare(b);
|
|
663
|
-
});
|
|
664
|
-
|
|
665
|
-
keys.forEach(key => {
|
|
666
|
-
const node = nodeMap[key];
|
|
667
|
-
const flatNode = {
|
|
668
|
-
id: node.path,
|
|
669
|
-
name: node.name,
|
|
670
|
-
path: node.path, // Relative path for selection
|
|
671
|
-
type: node.type,
|
|
672
|
-
depth: depth,
|
|
673
|
-
children: Object.keys(node.children).length > 0, // boolean for UI
|
|
674
|
-
expanded: this.search.length > 0 || depth < 1, // Auto expand on search or root
|
|
675
|
-
rawChildren: node.children // Keep ref for recursion
|
|
676
|
-
};
|
|
677
|
-
|
|
678
|
-
this.treeNodes.push(flatNode);
|
|
679
|
-
if (flatNode.children && flatNode.expanded) {
|
|
680
|
-
traverse(node.children, depth + 1);
|
|
681
|
-
}
|
|
682
|
-
});
|
|
683
|
-
};
|
|
684
|
-
|
|
685
|
-
traverse(root, 0);
|
|
686
|
-
},
|
|
687
|
-
|
|
688
|
-
// Re-calculate visible nodes (folding logic) without rebuilding everything
|
|
689
|
-
// But for simplicity in this SFC, we just rebuild the list when expanded changes.
|
|
690
|
-
toggleExpand(node) {
|
|
691
|
-
if (node.type !== 'folder') return;
|
|
692
|
-
|
|
693
|
-
// Find node in treeNodes and toggle
|
|
694
|
-
// Since treeNodes is flat and generated, we need to persist state or smarter logic.
|
|
695
|
-
// Simpler: Just modify the expanded state in the current flat list is tricky because children are dynamic.
|
|
696
|
-
// Better approach for this lightweight UI:
|
|
697
|
-
// We actually need a persistent "expanded" Set to survive re-renders if we want to be fancy.
|
|
698
|
-
// For now, let's just cheat: Update the boolean in the flat list works for collapsing,
|
|
699
|
-
// but expanding requires knowing the children.
|
|
700
|
-
|
|
701
|
-
// Let's reload the tree logic but using a \`expandedPaths\` set.
|
|
702
|
-
if (this.expandedPaths.has(node.path)) {
|
|
703
|
-
this.expandedPaths.delete(node.path);
|
|
704
|
-
} else {
|
|
705
|
-
this.expandedPaths.add(node.path);
|
|
706
|
-
}
|
|
707
|
-
this.refreshTreeVisibility();
|
|
708
|
-
},
|
|
709
623
|
|
|
710
624
|
// Alternative: Just use a computed property for \`visibleNodes\`.
|
|
711
625
|
// Since Alpine isn't React, we do this manually.
|
|
@@ -733,7 +647,8 @@ const startServer = async (rootDir) => {
|
|
|
733
647
|
|
|
734
648
|
this.rawFiles.forEach(file => {
|
|
735
649
|
if (this.search && !file.path.toLowerCase().includes(searchLower)) return;
|
|
736
|
-
|
|
650
|
+
// File path đã được chuẩn hóa '/' từ backend
|
|
651
|
+
const parts = file.path.split('/');
|
|
737
652
|
let current = this.rootObject;
|
|
738
653
|
parts.forEach((part, index) => {
|
|
739
654
|
if (!current[part]) {
|
|
@@ -834,9 +749,8 @@ const startServer = async (rootDir) => {
|
|
|
834
749
|
},
|
|
835
750
|
|
|
836
751
|
getAllDescendants(folderPath) {
|
|
837
|
-
// Simple filter from rawFiles
|
|
838
752
|
return this.rawFiles
|
|
839
|
-
.filter(f => f.path
|
|
753
|
+
.filter(f => f.path === folderPath || f.path.startsWith(folderPath + '/'))
|
|
840
754
|
.map(f => f.path);
|
|
841
755
|
},
|
|
842
756
|
|
|
@@ -869,7 +783,7 @@ const startServer = async (rootDir) => {
|
|
|
869
783
|
}
|
|
870
784
|
this.generating = true;
|
|
871
785
|
try {
|
|
872
|
-
const res = await fetch('
|
|
786
|
+
const res = await fetch('/api/prompt', {
|
|
873
787
|
method: 'POST',
|
|
874
788
|
headers: {'Content-Type': 'application/json'},
|
|
875
789
|
body: JSON.stringify({ filePaths: Array.from(this.selectedFiles), instruction: this.instruction })
|
|
@@ -896,7 +810,7 @@ const startServer = async (rootDir) => {
|
|
|
896
810
|
async applyPatch() {
|
|
897
811
|
this.applying = true;
|
|
898
812
|
try {
|
|
899
|
-
const res = await fetch('
|
|
813
|
+
const res = await fetch('/api/apply', {
|
|
900
814
|
method: 'POST',
|
|
901
815
|
headers: {'Content-Type': 'application/json'},
|
|
902
816
|
body: JSON.stringify({ filePaths: Array.from(this.selectedFiles), llmResponse: this.llmResponse })
|
package/dist/index.js
CHANGED
|
@@ -411,7 +411,7 @@ class RoomState {
|
|
|
411
411
|
return this.isConnected;
|
|
412
412
|
}
|
|
413
413
|
}
|
|
414
|
-
commander_1.program.version("0.0.
|
|
414
|
+
commander_1.program.version("0.0.30").description("Hapico CLI for project management");
|
|
415
415
|
commander_1.program
|
|
416
416
|
.command("clone <id>")
|
|
417
417
|
.description("Clone a project by ID")
|
package/dist/tools/vibe/index.js
CHANGED
|
@@ -17,7 +17,8 @@ const scanRecursively = async (dir, baseDir) => {
|
|
|
17
17
|
const list = await promises_1.default.readdir(dir, { withFileTypes: true });
|
|
18
18
|
for (const entry of list) {
|
|
19
19
|
const fullPath = node_path_1.default.join(dir, entry.name);
|
|
20
|
-
|
|
20
|
+
// Chuẩn hóa đường dẫn về forward slash để nhất quán giữa các hệ điều hành
|
|
21
|
+
const relativePath = node_path_1.default.relative(baseDir, fullPath).split(node_path_1.default.sep).join('/');
|
|
21
22
|
if (shouldIgnorePath(relativePath))
|
|
22
23
|
continue;
|
|
23
24
|
if (entry.isDirectory()) {
|
|
@@ -604,7 +605,8 @@ const startServer = async (rootDir) => {
|
|
|
604
605
|
|
|
605
606
|
async init() {
|
|
606
607
|
try {
|
|
607
|
-
|
|
608
|
+
// Sử dụng relative URL để tránh lỗi phân giải localhost/127.0.0.1 trên Windows
|
|
609
|
+
const res = await fetch('/api/files');
|
|
608
610
|
const data = await res.json();
|
|
609
611
|
this.rawFiles = data;
|
|
610
612
|
this.buildTree();
|
|
@@ -614,98 +616,10 @@ const startServer = async (rootDir) => {
|
|
|
614
616
|
this.loading = false;
|
|
615
617
|
}
|
|
616
618
|
|
|
617
|
-
// Search watcher
|
|
618
619
|
this.$watch('search', () => this.buildTree());
|
|
619
|
-
|
|
620
|
-
// Auto-generate prompt
|
|
621
620
|
this.$watch('instruction', () => this.debouncedFetchPrompt());
|
|
622
621
|
this.$watch('selectedFiles', () => this.debouncedFetchPrompt());
|
|
623
622
|
},
|
|
624
|
-
|
|
625
|
-
// ===========================
|
|
626
|
-
// Tree Logic
|
|
627
|
-
// ===========================
|
|
628
|
-
|
|
629
|
-
buildTree() {
|
|
630
|
-
// 1. Convert paths to object structure
|
|
631
|
-
const root = {};
|
|
632
|
-
const searchLower = this.search.toLowerCase();
|
|
633
|
-
|
|
634
|
-
this.rawFiles.forEach(file => {
|
|
635
|
-
if (this.search && !file.path.toLowerCase().includes(searchLower)) return;
|
|
636
|
-
|
|
637
|
-
const parts = file.path.split(/[\\\\/]/);
|
|
638
|
-
let current = root;
|
|
639
|
-
|
|
640
|
-
parts.forEach((part, index) => {
|
|
641
|
-
if (!current[part]) {
|
|
642
|
-
current[part] = {
|
|
643
|
-
name: part,
|
|
644
|
-
path: parts.slice(0, index + 1).join('/'),
|
|
645
|
-
children: {},
|
|
646
|
-
type: index === parts.length - 1 ? 'file' : 'folder',
|
|
647
|
-
fullPath: file.path // Only meaningful for file
|
|
648
|
-
};
|
|
649
|
-
}
|
|
650
|
-
current = current[part].children;
|
|
651
|
-
});
|
|
652
|
-
});
|
|
653
|
-
|
|
654
|
-
// 2. Flatten object structure into renderable list with depth
|
|
655
|
-
this.treeNodes = [];
|
|
656
|
-
const traverse = (nodeMap, depth) => {
|
|
657
|
-
// Sort: Folders first, then Files. Alphabetical.
|
|
658
|
-
const keys = Object.keys(nodeMap).sort((a, b) => {
|
|
659
|
-
const nodeA = nodeMap[a];
|
|
660
|
-
const nodeB = nodeMap[b];
|
|
661
|
-
if (nodeA.type !== nodeB.type) return nodeA.type === 'folder' ? -1 : 1;
|
|
662
|
-
return a.localeCompare(b);
|
|
663
|
-
});
|
|
664
|
-
|
|
665
|
-
keys.forEach(key => {
|
|
666
|
-
const node = nodeMap[key];
|
|
667
|
-
const flatNode = {
|
|
668
|
-
id: node.path,
|
|
669
|
-
name: node.name,
|
|
670
|
-
path: node.path, // Relative path for selection
|
|
671
|
-
type: node.type,
|
|
672
|
-
depth: depth,
|
|
673
|
-
children: Object.keys(node.children).length > 0, // boolean for UI
|
|
674
|
-
expanded: this.search.length > 0 || depth < 1, // Auto expand on search or root
|
|
675
|
-
rawChildren: node.children // Keep ref for recursion
|
|
676
|
-
};
|
|
677
|
-
|
|
678
|
-
this.treeNodes.push(flatNode);
|
|
679
|
-
if (flatNode.children && flatNode.expanded) {
|
|
680
|
-
traverse(node.children, depth + 1);
|
|
681
|
-
}
|
|
682
|
-
});
|
|
683
|
-
};
|
|
684
|
-
|
|
685
|
-
traverse(root, 0);
|
|
686
|
-
},
|
|
687
|
-
|
|
688
|
-
// Re-calculate visible nodes (folding logic) without rebuilding everything
|
|
689
|
-
// But for simplicity in this SFC, we just rebuild the list when expanded changes.
|
|
690
|
-
toggleExpand(node) {
|
|
691
|
-
if (node.type !== 'folder') return;
|
|
692
|
-
|
|
693
|
-
// Find node in treeNodes and toggle
|
|
694
|
-
// Since treeNodes is flat and generated, we need to persist state or smarter logic.
|
|
695
|
-
// Simpler: Just modify the expanded state in the current flat list is tricky because children are dynamic.
|
|
696
|
-
// Better approach for this lightweight UI:
|
|
697
|
-
// We actually need a persistent "expanded" Set to survive re-renders if we want to be fancy.
|
|
698
|
-
// For now, let's just cheat: Update the boolean in the flat list works for collapsing,
|
|
699
|
-
// but expanding requires knowing the children.
|
|
700
|
-
|
|
701
|
-
// Let's reload the tree logic but using a \`expandedPaths\` set.
|
|
702
|
-
if (this.expandedPaths.has(node.path)) {
|
|
703
|
-
this.expandedPaths.delete(node.path);
|
|
704
|
-
} else {
|
|
705
|
-
this.expandedPaths.add(node.path);
|
|
706
|
-
}
|
|
707
|
-
this.refreshTreeVisibility();
|
|
708
|
-
},
|
|
709
623
|
|
|
710
624
|
// Alternative: Just use a computed property for \`visibleNodes\`.
|
|
711
625
|
// Since Alpine isn't React, we do this manually.
|
|
@@ -733,7 +647,8 @@ const startServer = async (rootDir) => {
|
|
|
733
647
|
|
|
734
648
|
this.rawFiles.forEach(file => {
|
|
735
649
|
if (this.search && !file.path.toLowerCase().includes(searchLower)) return;
|
|
736
|
-
|
|
650
|
+
// File path đã được chuẩn hóa '/' từ backend
|
|
651
|
+
const parts = file.path.split('/');
|
|
737
652
|
let current = this.rootObject;
|
|
738
653
|
parts.forEach((part, index) => {
|
|
739
654
|
if (!current[part]) {
|
|
@@ -834,9 +749,8 @@ const startServer = async (rootDir) => {
|
|
|
834
749
|
},
|
|
835
750
|
|
|
836
751
|
getAllDescendants(folderPath) {
|
|
837
|
-
// Simple filter from rawFiles
|
|
838
752
|
return this.rawFiles
|
|
839
|
-
.filter(f => f.path
|
|
753
|
+
.filter(f => f.path === folderPath || f.path.startsWith(folderPath + '/'))
|
|
840
754
|
.map(f => f.path);
|
|
841
755
|
},
|
|
842
756
|
|
|
@@ -869,7 +783,7 @@ const startServer = async (rootDir) => {
|
|
|
869
783
|
}
|
|
870
784
|
this.generating = true;
|
|
871
785
|
try {
|
|
872
|
-
const res = await fetch('
|
|
786
|
+
const res = await fetch('/api/prompt', {
|
|
873
787
|
method: 'POST',
|
|
874
788
|
headers: {'Content-Type': 'application/json'},
|
|
875
789
|
body: JSON.stringify({ filePaths: Array.from(this.selectedFiles), instruction: this.instruction })
|
|
@@ -896,7 +810,7 @@ const startServer = async (rootDir) => {
|
|
|
896
810
|
async applyPatch() {
|
|
897
811
|
this.applying = true;
|
|
898
812
|
try {
|
|
899
|
-
const res = await fetch('
|
|
813
|
+
const res = await fetch('/api/apply', {
|
|
900
814
|
method: 'POST',
|
|
901
815
|
headers: {'Content-Type': 'application/json'},
|
|
902
816
|
body: JSON.stringify({ filePaths: Array.from(this.selectedFiles), llmResponse: this.llmResponse })
|
package/index.ts
CHANGED
package/package.json
CHANGED
package/tools/vibe/index.ts
CHANGED
|
@@ -16,7 +16,8 @@ const scanRecursively = async (
|
|
|
16
16
|
|
|
17
17
|
for (const entry of list) {
|
|
18
18
|
const fullPath = path.join(dir, entry.name);
|
|
19
|
-
|
|
19
|
+
// Chuẩn hóa đường dẫn về forward slash để nhất quán giữa các hệ điều hành
|
|
20
|
+
const relativePath = path.relative(baseDir, fullPath).split(path.sep).join('/');
|
|
20
21
|
|
|
21
22
|
if (shouldIgnorePath(relativePath)) continue;
|
|
22
23
|
|
|
@@ -662,7 +663,8 @@ const startServer = async (rootDir: string) => {
|
|
|
662
663
|
|
|
663
664
|
async init() {
|
|
664
665
|
try {
|
|
665
|
-
|
|
666
|
+
// Sử dụng relative URL để tránh lỗi phân giải localhost/127.0.0.1 trên Windows
|
|
667
|
+
const res = await fetch('/api/files');
|
|
666
668
|
const data = await res.json();
|
|
667
669
|
this.rawFiles = data;
|
|
668
670
|
this.buildTree();
|
|
@@ -672,98 +674,10 @@ const startServer = async (rootDir: string) => {
|
|
|
672
674
|
this.loading = false;
|
|
673
675
|
}
|
|
674
676
|
|
|
675
|
-
// Search watcher
|
|
676
677
|
this.$watch('search', () => this.buildTree());
|
|
677
|
-
|
|
678
|
-
// Auto-generate prompt
|
|
679
678
|
this.$watch('instruction', () => this.debouncedFetchPrompt());
|
|
680
679
|
this.$watch('selectedFiles', () => this.debouncedFetchPrompt());
|
|
681
680
|
},
|
|
682
|
-
|
|
683
|
-
// ===========================
|
|
684
|
-
// Tree Logic
|
|
685
|
-
// ===========================
|
|
686
|
-
|
|
687
|
-
buildTree() {
|
|
688
|
-
// 1. Convert paths to object structure
|
|
689
|
-
const root = {};
|
|
690
|
-
const searchLower = this.search.toLowerCase();
|
|
691
|
-
|
|
692
|
-
this.rawFiles.forEach(file => {
|
|
693
|
-
if (this.search && !file.path.toLowerCase().includes(searchLower)) return;
|
|
694
|
-
|
|
695
|
-
const parts = file.path.split(/[\\\\/]/);
|
|
696
|
-
let current = root;
|
|
697
|
-
|
|
698
|
-
parts.forEach((part, index) => {
|
|
699
|
-
if (!current[part]) {
|
|
700
|
-
current[part] = {
|
|
701
|
-
name: part,
|
|
702
|
-
path: parts.slice(0, index + 1).join('/'),
|
|
703
|
-
children: {},
|
|
704
|
-
type: index === parts.length - 1 ? 'file' : 'folder',
|
|
705
|
-
fullPath: file.path // Only meaningful for file
|
|
706
|
-
};
|
|
707
|
-
}
|
|
708
|
-
current = current[part].children;
|
|
709
|
-
});
|
|
710
|
-
});
|
|
711
|
-
|
|
712
|
-
// 2. Flatten object structure into renderable list with depth
|
|
713
|
-
this.treeNodes = [];
|
|
714
|
-
const traverse = (nodeMap, depth) => {
|
|
715
|
-
// Sort: Folders first, then Files. Alphabetical.
|
|
716
|
-
const keys = Object.keys(nodeMap).sort((a, b) => {
|
|
717
|
-
const nodeA = nodeMap[a];
|
|
718
|
-
const nodeB = nodeMap[b];
|
|
719
|
-
if (nodeA.type !== nodeB.type) return nodeA.type === 'folder' ? -1 : 1;
|
|
720
|
-
return a.localeCompare(b);
|
|
721
|
-
});
|
|
722
|
-
|
|
723
|
-
keys.forEach(key => {
|
|
724
|
-
const node = nodeMap[key];
|
|
725
|
-
const flatNode = {
|
|
726
|
-
id: node.path,
|
|
727
|
-
name: node.name,
|
|
728
|
-
path: node.path, // Relative path for selection
|
|
729
|
-
type: node.type,
|
|
730
|
-
depth: depth,
|
|
731
|
-
children: Object.keys(node.children).length > 0, // boolean for UI
|
|
732
|
-
expanded: this.search.length > 0 || depth < 1, // Auto expand on search or root
|
|
733
|
-
rawChildren: node.children // Keep ref for recursion
|
|
734
|
-
};
|
|
735
|
-
|
|
736
|
-
this.treeNodes.push(flatNode);
|
|
737
|
-
if (flatNode.children && flatNode.expanded) {
|
|
738
|
-
traverse(node.children, depth + 1);
|
|
739
|
-
}
|
|
740
|
-
});
|
|
741
|
-
};
|
|
742
|
-
|
|
743
|
-
traverse(root, 0);
|
|
744
|
-
},
|
|
745
|
-
|
|
746
|
-
// Re-calculate visible nodes (folding logic) without rebuilding everything
|
|
747
|
-
// But for simplicity in this SFC, we just rebuild the list when expanded changes.
|
|
748
|
-
toggleExpand(node) {
|
|
749
|
-
if (node.type !== 'folder') return;
|
|
750
|
-
|
|
751
|
-
// Find node in treeNodes and toggle
|
|
752
|
-
// Since treeNodes is flat and generated, we need to persist state or smarter logic.
|
|
753
|
-
// Simpler: Just modify the expanded state in the current flat list is tricky because children are dynamic.
|
|
754
|
-
// Better approach for this lightweight UI:
|
|
755
|
-
// We actually need a persistent "expanded" Set to survive re-renders if we want to be fancy.
|
|
756
|
-
// For now, let's just cheat: Update the boolean in the flat list works for collapsing,
|
|
757
|
-
// but expanding requires knowing the children.
|
|
758
|
-
|
|
759
|
-
// Let's reload the tree logic but using a \`expandedPaths\` set.
|
|
760
|
-
if (this.expandedPaths.has(node.path)) {
|
|
761
|
-
this.expandedPaths.delete(node.path);
|
|
762
|
-
} else {
|
|
763
|
-
this.expandedPaths.add(node.path);
|
|
764
|
-
}
|
|
765
|
-
this.refreshTreeVisibility();
|
|
766
|
-
},
|
|
767
681
|
|
|
768
682
|
// Alternative: Just use a computed property for \`visibleNodes\`.
|
|
769
683
|
// Since Alpine isn't React, we do this manually.
|
|
@@ -791,7 +705,8 @@ const startServer = async (rootDir: string) => {
|
|
|
791
705
|
|
|
792
706
|
this.rawFiles.forEach(file => {
|
|
793
707
|
if (this.search && !file.path.toLowerCase().includes(searchLower)) return;
|
|
794
|
-
|
|
708
|
+
// File path đã được chuẩn hóa '/' từ backend
|
|
709
|
+
const parts = file.path.split('/');
|
|
795
710
|
let current = this.rootObject;
|
|
796
711
|
parts.forEach((part, index) => {
|
|
797
712
|
if (!current[part]) {
|
|
@@ -892,9 +807,8 @@ const startServer = async (rootDir: string) => {
|
|
|
892
807
|
},
|
|
893
808
|
|
|
894
809
|
getAllDescendants(folderPath) {
|
|
895
|
-
// Simple filter from rawFiles
|
|
896
810
|
return this.rawFiles
|
|
897
|
-
.filter(f => f.path
|
|
811
|
+
.filter(f => f.path === folderPath || f.path.startsWith(folderPath + '/'))
|
|
898
812
|
.map(f => f.path);
|
|
899
813
|
},
|
|
900
814
|
|
|
@@ -927,7 +841,7 @@ const startServer = async (rootDir: string) => {
|
|
|
927
841
|
}
|
|
928
842
|
this.generating = true;
|
|
929
843
|
try {
|
|
930
|
-
const res = await fetch('
|
|
844
|
+
const res = await fetch('/api/prompt', {
|
|
931
845
|
method: 'POST',
|
|
932
846
|
headers: {'Content-Type': 'application/json'},
|
|
933
847
|
body: JSON.stringify({ filePaths: Array.from(this.selectedFiles), instruction: this.instruction })
|
|
@@ -954,7 +868,7 @@ const startServer = async (rootDir: string) => {
|
|
|
954
868
|
async applyPatch() {
|
|
955
869
|
this.applying = true;
|
|
956
870
|
try {
|
|
957
|
-
const res = await fetch('
|
|
871
|
+
const res = await fetch('/api/apply', {
|
|
958
872
|
method: 'POST',
|
|
959
873
|
headers: {'Content-Type': 'application/json'},
|
|
960
874
|
body: JSON.stringify({ filePaths: Array.from(this.selectedFiles), llmResponse: this.llmResponse })
|