jvcs 1.6.2 → 1.6.4
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/controllers/diff-engine/ui.js +77 -178
- package/package.json +2 -1
|
@@ -1,143 +1,81 @@
|
|
|
1
1
|
const blessed = require("blessed");
|
|
2
2
|
const aiAnalyzer = require("./aiAnalyzer");
|
|
3
|
-
const { marked } = require("marked");
|
|
4
|
-
const { TerminalRenderer } = require("marked-terminal");
|
|
5
|
-
|
|
6
|
-
marked.setOptions({
|
|
7
|
-
renderer: TerminalRenderer
|
|
8
|
-
});
|
|
9
3
|
|
|
10
4
|
function startUI(state) {
|
|
11
5
|
const screen = blessed.screen({
|
|
12
6
|
smartCSR: true,
|
|
13
7
|
title: "JVCS Diff Viewer"
|
|
14
|
-
})
|
|
8
|
+
});
|
|
15
9
|
|
|
16
10
|
// ---------------------------------------
|
|
17
|
-
//
|
|
11
|
+
// UI COMPONENTS
|
|
18
12
|
// ---------------------------------------
|
|
19
13
|
const header = blessed.box({
|
|
20
|
-
top: 0,
|
|
21
|
-
height: 1,
|
|
22
|
-
width: "100%",
|
|
14
|
+
top: 0, height: 1, width: "100%",
|
|
23
15
|
content: ` JVCS DIFF VIEW (${state.mode}) | ↑↓ Navigate | Enter Open | Esc Back | Tab Switch | Q Quit `,
|
|
24
|
-
style: {
|
|
25
|
-
fg: "white",
|
|
26
|
-
bg: "blue",
|
|
27
|
-
bold: true,
|
|
28
|
-
underline: true
|
|
29
|
-
},
|
|
16
|
+
style: { fg: "white", bg: "blue", bold: true, underline: true },
|
|
30
17
|
tags: true
|
|
31
|
-
})
|
|
32
|
-
screen.append(header)
|
|
18
|
+
});
|
|
19
|
+
screen.append(header);
|
|
33
20
|
|
|
34
|
-
// ---------------------------------------
|
|
35
|
-
// FILE LIST VIEW
|
|
36
|
-
// ---------------------------------------
|
|
37
21
|
const fileList = blessed.list({
|
|
38
|
-
top: 1,
|
|
39
|
-
|
|
40
|
-
width: "100%",
|
|
41
|
-
height: "100%-1",
|
|
42
|
-
keys: true,
|
|
43
|
-
vi: true,
|
|
44
|
-
border: "line",
|
|
22
|
+
top: 1, left: 0, width: "100%", height: "100%-1",
|
|
23
|
+
keys: true, vi: true, border: "line",
|
|
45
24
|
label: " Changed Files ",
|
|
46
|
-
style: {
|
|
47
|
-
selected: { bg: "blue" },
|
|
48
|
-
item: { fg: "white" }
|
|
49
|
-
},
|
|
25
|
+
style: { selected: { bg: "blue" }, item: { fg: "white" } },
|
|
50
26
|
tags: true
|
|
51
|
-
})
|
|
52
|
-
screen.append(fileList)
|
|
27
|
+
});
|
|
28
|
+
screen.append(fileList);
|
|
53
29
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
})
|
|
65
|
-
fileList.setItems(items)
|
|
66
|
-
fileList.select(state.selectedIndex)
|
|
67
|
-
fileList.focus()
|
|
68
|
-
screen.render()
|
|
69
|
-
}
|
|
30
|
+
const panelOptions = {
|
|
31
|
+
top: 1, height: "100%-1", border: "line",
|
|
32
|
+
scrollable: true, alwaysScroll: true, keys: true, vi: true,
|
|
33
|
+
scrollbar: { ch: "│", track: { bg: "black" }, style: { bg: "yellow" } },
|
|
34
|
+
padding: { left: 1, right: 1 }, tags: true
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const leftBox = blessed.box({ ...panelOptions, left: 0, width: "33%", label: " LEFT " });
|
|
38
|
+
const rightBox = blessed.box({ ...panelOptions, left: "33%", width: "34%", label: " RIGHT " });
|
|
39
|
+
const aiBox = blessed.box({ ...panelOptions, left: "67%", width: "33%", label: " AI ANALYSIS " });
|
|
70
40
|
|
|
71
41
|
// ---------------------------------------
|
|
72
|
-
//
|
|
42
|
+
// HELPERS
|
|
73
43
|
// ---------------------------------------
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
vi: true,
|
|
85
|
-
scrollbar: { ch: "│", track: { bg: "black" }, style: { bg: "yellow" } },
|
|
86
|
-
padding: { left: 1, right: 1 },
|
|
87
|
-
tags: true
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
const rightBox = blessed.box({
|
|
91
|
-
top: 1,
|
|
92
|
-
left: "33%",
|
|
93
|
-
width: "34%",
|
|
94
|
-
height: "100%-1",
|
|
95
|
-
border: "line",
|
|
96
|
-
label: " RIGHT ",
|
|
97
|
-
scrollable: true,
|
|
98
|
-
alwaysScroll: true,
|
|
99
|
-
keys: true,
|
|
100
|
-
vi: true,
|
|
101
|
-
scrollbar: { ch: "│", track: { bg: "black" }, style: { bg: "yellow" } },
|
|
102
|
-
padding: { left: 1, right: 1 },
|
|
103
|
-
tags: true
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
const aiBox = blessed.box({
|
|
107
|
-
top: 1,
|
|
108
|
-
left: "67%",
|
|
109
|
-
width: "33%",
|
|
110
|
-
height: "100%-1",
|
|
111
|
-
border: "line",
|
|
112
|
-
label: " AI ANALYSIS ",
|
|
113
|
-
scrollable: true,
|
|
114
|
-
alwaysScroll: true,
|
|
115
|
-
keys: true,
|
|
116
|
-
vi: true,
|
|
117
|
-
scrollbar: { ch: "│", track: { bg: "black" }, style: { bg: "yellow" } },
|
|
118
|
-
padding: { left: 1, right: 1 },
|
|
119
|
-
tags: true
|
|
120
|
-
})
|
|
44
|
+
function stripMarkdown(text) {
|
|
45
|
+
return text
|
|
46
|
+
.replace(/#{1,6}\s/g, "") // Remove headers
|
|
47
|
+
.replace(/\*\*(.*?)\*\*/g, "$1") // Bold to text
|
|
48
|
+
.replace(/\*(.*?)\*/g, "$1") // Italics to text
|
|
49
|
+
.replace(/`(.*?)`/g, "$1") // Code ticks to text
|
|
50
|
+
.replace(/\[(.*?)\]\(.*?\)/g, "$1") // Links to text
|
|
51
|
+
.replace(/^>\s/gm, "") // Blockquotes
|
|
52
|
+
.replace(/^- /gm, "• "); // List markers to bullets
|
|
53
|
+
}
|
|
121
54
|
|
|
122
55
|
function colorDiffContent(content, type) {
|
|
56
|
+
if (!content) return "";
|
|
123
57
|
return content.split("\n").map(line => {
|
|
124
|
-
if(type === "added") return `{green-fg}+ ${line}{/green-fg}
|
|
125
|
-
if(type === "removed") return `{red-fg}- ${line}{/red-fg}
|
|
126
|
-
return ` ${line}
|
|
127
|
-
}).join("\n")
|
|
58
|
+
if(type === "added") return `{green-fg}+ ${line}{/green-fg}`;
|
|
59
|
+
if(type === "removed") return `{red-fg}- ${line}{/red-fg}`;
|
|
60
|
+
return ` ${line}`;
|
|
61
|
+
}).join("\n");
|
|
128
62
|
}
|
|
129
63
|
|
|
64
|
+
// ---------------------------------------
|
|
65
|
+
// RENDER LOGIC
|
|
66
|
+
// ---------------------------------------
|
|
130
67
|
async function renderFileView() {
|
|
131
|
-
const file = state.getCurrentFile()
|
|
132
|
-
if (!file) return
|
|
133
|
-
|
|
134
|
-
leftBox.setContent(colorDiffContent(file.leftContent, "removed"))
|
|
135
|
-
rightBox.setContent(colorDiffContent(file.rightContent, "added"))
|
|
68
|
+
const file = state.getCurrentFile();
|
|
69
|
+
if (!file) return;
|
|
136
70
|
|
|
71
|
+
leftBox.setContent(colorDiffContent(file.leftContent, "removed"));
|
|
72
|
+
rightBox.setContent(colorDiffContent(file.rightContent, "added"));
|
|
137
73
|
aiBox.setContent("AI Analysis: Loading...");
|
|
138
|
-
|
|
139
|
-
screen.append(
|
|
140
|
-
screen.append(
|
|
74
|
+
|
|
75
|
+
screen.append(leftBox);
|
|
76
|
+
screen.append(rightBox);
|
|
77
|
+
screen.append(aiBox);
|
|
78
|
+
|
|
141
79
|
updateActiveTabHighlight();
|
|
142
80
|
screen.render();
|
|
143
81
|
|
|
@@ -147,85 +85,46 @@ function startUI(state) {
|
|
|
147
85
|
leftContent: file.leftContent,
|
|
148
86
|
rightContent: file.rightContent,
|
|
149
87
|
mode: state.mode
|
|
150
|
-
})
|
|
88
|
+
});
|
|
151
89
|
|
|
152
|
-
|
|
153
|
-
aiBox.setContent(
|
|
154
|
-
screen.render()
|
|
90
|
+
// Convert to clean text for terminal
|
|
91
|
+
aiBox.setContent(stripMarkdown(aiSummary));
|
|
92
|
+
screen.render();
|
|
155
93
|
}
|
|
156
94
|
catch(error) {
|
|
157
|
-
aiBox.setContent(`AI Analysis failed: ${error.message || error}`);
|
|
158
|
-
screen.render()
|
|
95
|
+
aiBox.setContent(`{red-fg}AI Analysis failed: ${error.message || error}{/red-fg}`);
|
|
96
|
+
screen.render();
|
|
159
97
|
}
|
|
160
98
|
}
|
|
161
99
|
|
|
162
|
-
function destroyFileView() {
|
|
163
|
-
leftBox.detach()
|
|
164
|
-
rightBox.detach()
|
|
165
|
-
aiBox.detach()
|
|
166
|
-
screen.render()
|
|
167
|
-
}
|
|
168
|
-
|
|
169
100
|
function updateActiveTabHighlight() {
|
|
170
|
-
leftBox.style.border.fg = state.activeTab === 0 ? "yellow" : "grey"
|
|
171
|
-
rightBox.style.border.fg = state.activeTab === 1 ? "yellow" : "grey"
|
|
172
|
-
aiBox.style.border.fg = state.activeTab === 2 ? "yellow" : "grey"
|
|
173
|
-
|
|
174
|
-
if(state.activeTab ===
|
|
175
|
-
else
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
screen.render()
|
|
101
|
+
leftBox.style.border.fg = state.activeTab === 0 ? "yellow" : "grey";
|
|
102
|
+
rightBox.style.border.fg = state.activeTab === 1 ? "yellow" : "grey";
|
|
103
|
+
aiBox.style.border.fg = state.activeTab === 2 ? "yellow" : "grey";
|
|
104
|
+
if(state.activeTab === 0) leftBox.focus();
|
|
105
|
+
else if(state.activeTab === 1) rightBox.focus();
|
|
106
|
+
else aiBox.focus();
|
|
107
|
+
screen.render();
|
|
179
108
|
}
|
|
180
109
|
|
|
181
110
|
// ---------------------------------------
|
|
182
|
-
//
|
|
111
|
+
// INPUT HANDLING
|
|
183
112
|
// ---------------------------------------
|
|
184
|
-
screen.key(["q", "C-c"], () => process.exit(0))
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
fileList.key(["
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
renderFileList()
|
|
191
|
-
}
|
|
192
|
-
})
|
|
193
|
-
fileList.key(["down"], () => {
|
|
194
|
-
if(state.selectedIndex < state.files.length - 1) {
|
|
195
|
-
state.selectFile(state.selectedIndex + 1)
|
|
196
|
-
renderFileList()
|
|
197
|
-
}
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
// Enter → Open file view
|
|
201
|
-
fileList.key(["enter"], async () => {
|
|
202
|
-
state.goToFileView()
|
|
203
|
-
fileList.detach()
|
|
204
|
-
await renderFileView()
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
// ESC → Back to list
|
|
208
|
-
screen.key(["escape"], () => {
|
|
209
|
-
if(state.screen === "FILE_VIEW") {
|
|
210
|
-
state.goToListView()
|
|
211
|
-
destroyFileView()
|
|
212
|
-
screen.append(fileList)
|
|
213
|
-
renderFileList()
|
|
214
|
-
}
|
|
215
|
-
})
|
|
113
|
+
screen.key(["q", "C-c"], () => process.exit(0));
|
|
114
|
+
fileList.key(["up"], () => { if(state.selectedIndex > 0) { state.selectFile(state.selectedIndex - 1); renderFileList(); }});
|
|
115
|
+
fileList.key(["down"], () => { if(state.selectedIndex < state.files.length - 1) { state.selectFile(state.selectedIndex + 1); renderFileList(); }});
|
|
116
|
+
fileList.key(["enter"], async () => { state.goToFileView(); fileList.detach(); await renderFileView(); });
|
|
117
|
+
screen.key(["escape"], () => { if(state.screen === "FILE_VIEW") { state.goToListView(); leftBox.detach(); rightBox.detach(); aiBox.detach(); screen.append(fileList); renderFileList(); }});
|
|
118
|
+
screen.key(["tab"], () => { if(state.screen === "FILE_VIEW") { state.switchTab((state.activeTab + 1) % 3); updateActiveTabHighlight(); }});
|
|
216
119
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
})
|
|
120
|
+
function renderFileList() {
|
|
121
|
+
fileList.setItems(state.files.map(f => `${f.status === "added" ? "{green-fg}" : f.status === "deleted" ? "{red-fg}" : "{yellow-fg}"}${f.status.toUpperCase()}{/} ${f.path}`));
|
|
122
|
+
fileList.select(state.selectedIndex);
|
|
123
|
+
fileList.focus();
|
|
124
|
+
screen.render();
|
|
125
|
+
}
|
|
224
126
|
|
|
225
|
-
|
|
226
|
-
// INITIAL RENDER
|
|
227
|
-
// ---------------------------------------
|
|
228
|
-
renderFileList()
|
|
127
|
+
renderFileList();
|
|
229
128
|
}
|
|
230
129
|
|
|
231
|
-
module.exports = startUI
|
|
130
|
+
module.exports = startUI;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jvcs",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.4",
|
|
4
4
|
"bin": {
|
|
5
5
|
"jvcs": "index.js"
|
|
6
6
|
},
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"googleapis": "^164.1.0",
|
|
17
17
|
"inquirer": "^8.2.7",
|
|
18
18
|
"marked": "^15.0.12",
|
|
19
|
+
"marked-plaintext": "^0.0.2",
|
|
19
20
|
"marked-terminal": "^7.3.0",
|
|
20
21
|
"minimatch": "^10.2.4",
|
|
21
22
|
"uuid": "^13.0.0",
|