Skip to content

Commit e00a94c

Browse files
committed
Message organization and abstraction for diag summary
Signed-off-by: Ian Bolton <[email protected]>
1 parent 9550946 commit e00a94c

File tree

6 files changed

+222
-104
lines changed

6 files changed

+222
-104
lines changed

vscode/src/utilities/ModifiedFiles/processMessage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ const resetStuckAnalysisFlags = (state: ExtensionState): void => {
5151
const createTasksMessage = (tasks: TasksList): { message: string; diagnosticSummary: any } => {
5252
const diagnosticSummary = summarizeTasksStructured(tasks);
5353
return {
54-
message: `It appears that my fixes caused following issues. Please review the issues below and select which ones you'd like me to fix. Do you want me to continue fixing the selected issues?`,
54+
message: `It appears that my fixes caused following issues. Please review the issues below and select which ones you'd like me to fix. `,
5555
diagnosticSummary: {
5656
summary: diagnosticSummary.summary,
5757
issuesByFile: Object.fromEntries(diagnosticSummary.issuesByFile),

webview-ui/src/components/ResolutionsPage/DiagnosticIssuesView.tsx

Lines changed: 10 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,19 @@ interface DiagnosticSummary {
1616
totalIssues: number;
1717
}
1818

19-
interface QuickResponse {
20-
id: string;
21-
content: string;
22-
messageToken: string;
23-
}
24-
2519
interface DiagnosticIssuesViewProps {
2620
diagnosticSummary: DiagnosticSummary;
2721
onIssueSelectionChange?: (selectedIssues: DiagnosticIssue[]) => void;
28-
quickResponses?: QuickResponse[];
2922
isProcessing?: boolean;
3023
}
3124

3225
export const DiagnosticIssuesView: React.FC<DiagnosticIssuesViewProps> = ({
3326
diagnosticSummary,
3427
onIssueSelectionChange,
35-
quickResponses,
3628
isProcessing = false,
3729
}) => {
3830
const [selectedIssues, setSelectedIssues] = useState<Set<string>>(new Set());
3931
const [expandedFiles, setExpandedFiles] = useState<Set<string>>(new Set());
40-
const [selectedResponse, setSelectedResponse] = useState<string | null>(null);
4132

4233
const handleIssueToggle = useCallback(
4334
(issueId: string) => {
@@ -104,26 +95,6 @@ export const DiagnosticIssuesView: React.FC<DiagnosticIssuesViewProps> = ({
10495
}
10596
}, [onIssueSelectionChange]);
10697

107-
const handleQuickResponse = useCallback(
108-
(responseId: string, messageToken: string) => {
109-
setSelectedResponse(responseId);
110-
window.vscode.postMessage({
111-
type: "QUICK_RESPONSE",
112-
payload: {
113-
responseId,
114-
messageToken,
115-
selectedIssues: Array.from(selectedIssues),
116-
},
117-
});
118-
},
119-
[selectedIssues, diagnosticSummary],
120-
);
121-
122-
// Hide the diagnostic view after a response is selected
123-
if (selectedResponse !== null) {
124-
return null;
125-
}
126-
12798
return (
12899
<div className="diagnostic-issues-view">
129100
<div className="diagnostic-header">
@@ -224,42 +195,16 @@ export const DiagnosticIssuesView: React.FC<DiagnosticIssuesViewProps> = ({
224195

225196
{selectedIssues.size > 0 && (
226197
<div className="selection-summary">
227-
<div>{selectedIssues.size} issue(s) selected</div>
228-
<div className="selected-issues-details">
229-
{Object.entries(diagnosticSummary.issuesByFile).map(([filename, issues]) => {
230-
const selectedInFile = issues.filter((issue) => selectedIssues.has(issue.id));
231-
if (selectedInFile.length === 0) {
232-
return null;
233-
}
234-
235-
return (
236-
<div key={filename} className="selected-file">
237-
{filename}: {selectedInFile.length} issue(s)
238-
</div>
239-
);
240-
})}
241-
</div>
242-
</div>
243-
)}
244-
245-
{quickResponses && quickResponses.length > 0 && (
246-
<div className="diagnostic-footer">
247-
<div className="quick-responses">
248-
{quickResponses.map((response) => (
249-
<button
250-
key={response.id}
251-
className={`quick-response-btn ${selectedResponse === response.id ? "selected" : ""}`}
252-
onClick={() => handleQuickResponse(response.id, response.messageToken)}
253-
disabled={
254-
isProcessing ||
255-
selectedResponse !== null ||
256-
(response.id === "yes" && selectedIssues.size === 0)
257-
}
258-
>
259-
{response.content}
260-
</button>
261-
))}
262-
</div>
198+
{(() => {
199+
const selectedFiles = Object.entries(diagnosticSummary.issuesByFile).filter(([filename, issues]) =>
200+
issues.some(issue => selectedIssues.has(issue.id))
201+
);
202+
return (
203+
<div>
204+
{selectedIssues.size} issue{selectedIssues.size !== 1 ? 's' : ''} selected across {selectedFiles.length} file{selectedFiles.length !== 1 ? 's' : ''}
205+
</div>
206+
);
207+
})()}
263208
</div>
264209
)}
265210
</div>
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import React, { useState } from "react";
2+
import { Message } from "@patternfly/chatbot";
3+
import { QuickResponse } from "@editor-extensions/shared";
4+
import rehypeRaw from "rehype-raw";
5+
import rehypeSanitize from "rehype-sanitize";
6+
import botAv from "./bot_avatar.svg?inline";
7+
import DiagnosticIssuesView from "./DiagnosticIssuesView";
8+
import { quickResponse } from "../../hooks/actions";
9+
10+
// Import diagnostic types from the shared types file
11+
interface DiagnosticIssue {
12+
id: string;
13+
message: string;
14+
uri: string;
15+
filename: string;
16+
selected?: boolean;
17+
}
18+
19+
interface DiagnosticSummary {
20+
summary: string;
21+
issuesByFile: Record<string, DiagnosticIssue[]>;
22+
totalIssues: number;
23+
}
24+
25+
interface QuickResponseWithToken extends QuickResponse {
26+
messageToken: string;
27+
}
28+
29+
interface DiagnosticMessageProps {
30+
content?: string;
31+
extraContent?: React.ReactNode;
32+
isLoading?: boolean;
33+
timestamp?: string | Date;
34+
quickResponses?: QuickResponseWithToken[];
35+
isProcessing?: boolean;
36+
diagnosticSummary: DiagnosticSummary;
37+
question?: string; // Optional question to provide context for Yes/No buttons
38+
}
39+
40+
export const DiagnosticMessage: React.FC<DiagnosticMessageProps> = ({
41+
content,
42+
extraContent,
43+
isLoading,
44+
timestamp = new Date(),
45+
quickResponses,
46+
isProcessing = false,
47+
diagnosticSummary,
48+
question,
49+
}) => {
50+
const [selectedResponse, setSelectedResponse] = useState<string | null>(null);
51+
const [selectedIssues, setSelectedIssues] = useState<DiagnosticIssue[]>([]);
52+
53+
// Don't render anything if there's no content and no extra content
54+
// This prevents "phantom" blank messages from appearing
55+
if (!content && !extraContent && !quickResponses?.length) {
56+
return null;
57+
}
58+
59+
const formatTimestamp = (time: string | Date): string => {
60+
const date = typeof time === "string" ? new Date(time) : time;
61+
return date.toLocaleTimeString("en-US", {
62+
hour: "2-digit",
63+
minute: "2-digit",
64+
second: "2-digit",
65+
});
66+
};
67+
68+
const handleQuickResponse = (responseId: string, messageToken: string) => {
69+
// Update state to reflect selected response
70+
// Note: Consider using React.memo or other optimization techniques if flickering persists
71+
setSelectedResponse(responseId);
72+
window.vscode.postMessage(
73+
quickResponse({
74+
responseId,
75+
messageToken,
76+
selectedIssues: selectedIssues.map(issue => issue.id), // Include selected issues
77+
})
78+
);
79+
};
80+
81+
const handleIssueSelectionChange = (issues: DiagnosticIssue[]) => {
82+
setSelectedIssues(issues);
83+
};
84+
85+
return (
86+
<>
87+
<Message
88+
timestamp={formatTimestamp(timestamp)}
89+
name="Konveyor"
90+
role="bot"
91+
avatar={botAv}
92+
content={content || ""}
93+
additionalRehypePlugins={[rehypeRaw, rehypeSanitize]}
94+
/>
95+
96+
<DiagnosticIssuesView
97+
diagnosticSummary={diagnosticSummary}
98+
onIssueSelectionChange={handleIssueSelectionChange}
99+
isProcessing={isProcessing}
100+
/>
101+
102+
{question && quickResponses && quickResponses.length > 0 && (
103+
<Message
104+
timestamp={formatTimestamp(timestamp)}
105+
name="Konveyor"
106+
role="bot"
107+
avatar={botAv}
108+
content={question}
109+
quickResponses={
110+
quickResponses.map((response) => ({
111+
...response,
112+
onClick: () => {
113+
handleQuickResponse(response.id, response.messageToken);
114+
},
115+
isDisabled: response.isDisabled || isProcessing || selectedResponse !== null ||
116+
(response.id === "yes" && selectedIssues.length === 0),
117+
content: response.content,
118+
}))
119+
}
120+
additionalRehypePlugins={[rehypeRaw, rehypeSanitize]}
121+
/>
122+
)}
123+
</>
124+
);
125+
};
126+
127+
export default DiagnosticMessage;

webview-ui/src/components/ResolutionsPage/ReceivedMessage.tsx

Lines changed: 37 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { QuickResponse } from "@editor-extensions/shared";
44
import rehypeRaw from "rehype-raw";
55
import rehypeSanitize from "rehype-sanitize";
66
import botAv from "./bot_avatar.svg?inline";
7-
import DiagnosticIssuesView from "./DiagnosticIssuesView";
7+
import DiagnosticMessage from "./DiagnosticMessage";
88
import { quickResponse } from "../../hooks/actions";
99

1010
// Import diagnostic types from the shared types file
@@ -34,6 +34,7 @@ interface ReceivedMessageProps {
3434
quickResponses?: QuickResponseWithToken[];
3535
isProcessing?: boolean;
3636
diagnosticSummary?: DiagnosticSummary;
37+
question?: string; // Optional question to provide context for Yes/No buttons
3738
}
3839

3940
export const ReceivedMessage: React.FC<ReceivedMessageProps> = ({
@@ -44,9 +45,25 @@ export const ReceivedMessage: React.FC<ReceivedMessageProps> = ({
4445
quickResponses,
4546
isProcessing = false,
4647
diagnosticSummary,
48+
question,
4749
}) => {
4850
const [selectedResponse, setSelectedResponse] = useState<string | null>(null);
49-
const [selectedIssues, setSelectedIssues] = useState<DiagnosticIssue[]>([]);
51+
52+
// If we have diagnostic summary, use the DiagnosticMessage component
53+
if (diagnosticSummary) {
54+
return (
55+
<DiagnosticMessage
56+
content={content}
57+
extraContent={extraContent}
58+
isLoading={isLoading}
59+
timestamp={timestamp}
60+
quickResponses={quickResponses}
61+
isProcessing={isProcessing}
62+
diagnosticSummary={diagnosticSummary}
63+
question={question}
64+
/>
65+
);
66+
}
5067

5168
// Don't render anything if there's no content and no extra content
5269
// This prevents "phantom" blank messages from appearing
@@ -71,47 +88,29 @@ export const ReceivedMessage: React.FC<ReceivedMessageProps> = ({
7188
quickResponse({
7289
responseId,
7390
messageToken,
74-
selectedIssues: selectedIssues.map(issue => issue.id), // Include selected issues
7591
})
7692
);
7793
};
7894

79-
const handleIssueSelectionChange = (issues: DiagnosticIssue[]) => {
80-
setSelectedIssues(issues);
81-
};
82-
8395
return (
84-
<>
85-
<Message
86-
timestamp={formatTimestamp(timestamp)}
87-
name="Konveyor"
88-
role="bot"
89-
avatar={botAv}
90-
content={content || ""} // Ensure content is never undefined
91-
quickResponses={
92-
diagnosticSummary
93-
? undefined // Don't show quick responses in Message when we have diagnostic summary
94-
: quickResponses?.map((response) => ({
95-
...response,
96-
onClick: () => {
97-
handleQuickResponse(response.id, response.messageToken);
98-
},
99-
isDisabled: response.isDisabled || isProcessing || selectedResponse !== null,
100-
content: response.content,
101-
}))
102-
}
103-
additionalRehypePlugins={[rehypeRaw, rehypeSanitize]}
104-
/>
105-
106-
{diagnosticSummary && (
107-
<DiagnosticIssuesView
108-
diagnosticSummary={diagnosticSummary}
109-
onIssueSelectionChange={handleIssueSelectionChange}
110-
quickResponses={quickResponses}
111-
isProcessing={isProcessing}
112-
/>
113-
)}
114-
</>
96+
<Message
97+
timestamp={formatTimestamp(timestamp)}
98+
name="Konveyor"
99+
role="bot"
100+
avatar={botAv}
101+
content={content || ""} // Ensure content is never undefined
102+
quickResponses={
103+
quickResponses?.map((response) => ({
104+
...response,
105+
onClick: () => {
106+
handleQuickResponse(response.id, response.messageToken);
107+
},
108+
isDisabled: response.isDisabled || isProcessing || selectedResponse !== null,
109+
content: response.content,
110+
}))
111+
}
112+
additionalRehypePlugins={[rehypeRaw, rehypeSanitize]}
113+
/>
115114
);
116115
};
117116

0 commit comments

Comments
 (0)