Skip to content

Commit 753e16f

Browse files
feat(ui): Enhance chat UI with dynamic styles and code highlighting
- Implemented dynamic CSS updates in the chat UI to reflect theme and font changes. - Added code syntax highlighting using highlight.js in webview. - Introduced a copy button for code blocks to copy the code to clipboard.
1 parent 2c3a9ad commit 753e16f

File tree

10 files changed

+124
-213
lines changed

10 files changed

+124
-213
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@
182182
},
183183
"configuration": [
184184
{
185-
"title": "CodeBuddy, Ola",
185+
"title": "CodeBuddy",
186186
"properties": {
187187
"generativeAi.option": {
188188
"type": "string",

src/extension.ts

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const {
4343

4444
const connectDB = async () => {
4545
await dbManager.connect(
46-
"file:/Users/olasunkanmi/Documents/Github/codebuddy/patterns/dev.db",
46+
"file:/Users/olasunkanmioyinlola/Documents/Apps/codebuddy/patterns/aii.db"
4747
);
4848
};
4949

@@ -57,7 +57,7 @@ export async function activate(context: vscode.ExtensionContext) {
5757
const session: vscode.AuthenticationSession | undefined =
5858
await credentials.getSession();
5959
vscode.window.showInformationMessage(
60-
`Logged into GitHub as ${session?.account.label}`,
60+
`Logged into GitHub as ${session?.account.label}`
6161
);
6262
Memory.getInstance();
6363
await connectDB();
@@ -75,11 +75,11 @@ export async function activate(context: vscode.ExtensionContext) {
7575
// const names = await fileUpload.getFileNames();
7676
// console.log(files, names);
7777

78-
const index = CodeIndexingService.createInstance();
78+
// const index = CodeIndexingService.createInstance();
7979
// Get each of the folders and call the next line for each
80-
const result = await index.buildFunctionStructureMap();
80+
// const result = await index.buildFunctionStructureMap();
8181
// await index.insertFunctionsinDB();
82-
console.log(result);
82+
// console.log(result);
8383
const {
8484
comment,
8585
review,
@@ -97,49 +97,49 @@ export async function activate(context: vscode.ExtensionContext) {
9797
} = OLA_ACTIONS;
9898
const getComment = new Comments(
9999
`${USER_MESSAGE} generates the code comments...`,
100-
context,
100+
context
101101
);
102102
const getInLineChat = new InLineChat(
103103
`${USER_MESSAGE} generates a response...`,
104-
context,
104+
context
105105
);
106106
const generateOptimizeCode = new OptimizeCode(
107107
`${USER_MESSAGE} optimizes the code...`,
108-
context,
108+
context
109109
);
110110
const generateRefactoredCode = new RefactorCode(
111111
`${USER_MESSAGE} refactors the code...`,
112-
context,
112+
context
113113
);
114114
const explainCode = new ExplainCode(
115115
`${USER_MESSAGE} explains the code...`,
116-
context,
116+
context
117117
);
118118
const generateReview = new ReviewCode(
119119
`${USER_MESSAGE} reviews the code...`,
120-
context,
120+
context
121121
);
122122
const codeChartGenerator = new CodeChartGenerator(
123123
`${USER_MESSAGE} creates the code chart...`,
124-
context,
124+
context
125125
);
126126
const codePattern = fileUpload;
127127
const knowledgeBase = new ReadFromKnowledgeBase(
128128
`${USER_MESSAGE} generate your code pattern...`,
129-
context,
129+
context
130130
);
131131
const generateCommitMessage = new GenerateCommitMessage(
132132
`${USER_MESSAGE} generates a commit message...`,
133-
context,
133+
context
134134
);
135135
const generateInterviewQuestions = new InterviewMe(
136136
`${USER_MESSAGE} generates interview questions...`,
137-
context,
137+
context
138138
);
139139

140140
const generateUnitTests = new GenerateUnitTest(
141141
`${USER_MESSAGE} generates unit tests...`,
142-
context,
142+
context
143143
);
144144

145145
const actionMap = {
@@ -153,7 +153,7 @@ export async function activate(context: vscode.ExtensionContext) {
153153
new FixError(
154154
`${USER_MESSAGE} finds a solution to the error...`,
155155
context,
156-
errorMessage,
156+
errorMessage
157157
).execute(errorMessage),
158158
[explain]: async () => await explainCode.execute(),
159159
[pattern]: async () => await codePattern.uploadFileHandler(),
@@ -165,15 +165,15 @@ export async function activate(context: vscode.ExtensionContext) {
165165
};
166166

167167
const subscriptions: vscode.Disposable[] = Object.entries(actionMap).map(
168-
([action, handler]) => vscode.commands.registerCommand(action, handler),
168+
([action, handler]) => vscode.commands.registerCommand(action, handler)
169169
);
170170

171171
const selectedGenerativeAiModel = getConfigValue("generativeAi.option");
172172

173173
const quickFix = new CodeActionsProvider();
174174
quickFixCodeAction = vscode.languages.registerCodeActionsProvider(
175175
{ scheme: "file", language: "*" },
176-
quickFix,
176+
quickFix
177177
);
178178

179179
agentEventEmmitter = new EventEmitter();
@@ -216,13 +216,13 @@ export async function activate(context: vscode.ExtensionContext) {
216216
webviewProviderClass,
217217
subscriptions,
218218
quickFixCodeAction,
219-
agentEventEmmitter,
219+
agentEventEmmitter
220220
);
221221
}
222222
} catch (error) {
223223
Memory.clear();
224224
vscode.window.showErrorMessage(
225-
"An Error occured while setting up generative AI model",
225+
"An Error occured while setting up generative AI model"
226226
);
227227
console.log(error);
228228
}

src/providers/base.ts

Lines changed: 17 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export abstract class BaseWebViewProvider implements vscode.Disposable {
2121
private readonly _extensionUri: vscode.Uri,
2222
protected readonly apiKey: string,
2323
protected readonly generativeAiModel: string,
24-
context: vscode.ExtensionContext,
24+
context: vscode.ExtensionContext
2525
) {
2626
this._context = context;
2727
this.orchestrator = Orchestrator.getInstance();
@@ -31,9 +31,7 @@ export abstract class BaseWebViewProvider implements vscode.Disposable {
3131
this.orchestrator.onThinking(this.handleModelResponseEvent.bind(this)),
3232
this.orchestrator.onUpdate(this.handleModelResponseEvent.bind(this)),
3333
this.orchestrator.onError(this.handleModelResponseEvent.bind(this)),
34-
this.orchestrator.onSecretChange(
35-
this.handleModelResponseEvent.bind(this),
36-
),
34+
this.orchestrator.onSecretChange(this.handleModelResponseEvent.bind(this))
3735
);
3836
}
3937

@@ -54,59 +52,42 @@ export abstract class BaseWebViewProvider implements vscode.Disposable {
5452

5553
if (!this.apiKey) {
5654
vscode.window.showErrorMessage(
57-
"API key not configured. Check your settings.",
55+
"API key not configured. Check your settings."
5856
);
5957
return;
6058
}
6159
this.setWebviewHtml(this.currentWebView);
6260
this.setupMessageHandler(
6361
this.apiKey,
6462
this.generativeAiModel,
65-
this.currentWebView,
63+
this.currentWebView
6664
);
6765

6866
setTimeout(() => {
69-
this.currentWebView?.webview.postMessage({ type: "updateStyles", payload: getChatCss()});
70-
this.currentWebView?.webview.postMessage({ type: "modelUpdate", payload: { model: getConfigValue("generativeAi.option")} });
67+
this.currentWebView?.webview.postMessage({
68+
type: "updateStyles",
69+
payload: getChatCss(),
70+
});
71+
// this.currentWebView?.webview.postMessage({
72+
// type: "modelUpdate",
73+
// payload: { model: getConfigValue("generativeAi.option") },
74+
// });
7175
}, 500);
72-
73-
vscode.workspace.onDidChangeConfiguration((event) => {
74-
console.log("onDidChangeConfiguration event details is: ");
75-
console.dir(event);
76-
77-
let chatViewThemeChange = event.affectsConfiguration("chatview.theme");
78-
let chatViewFontSizeChange = event.affectsConfiguration("chatview.font.size");
79-
let fontFamilyChange = event.affectsConfiguration("font.family");
80-
console.log("Checking if extension config changes are made to chatview.theme: ", chatViewThemeChange);
81-
console.log("Checking if extension config changes are made to chatview.font.size: ", chatViewFontSizeChange);
82-
console.log("Checking if extension config changes are made to font.family: ", fontFamilyChange);
83-
84-
if(chatViewThemeChange || chatViewFontSizeChange || fontFamilyChange) {
85-
this.currentWebView?.webview.postMessage({type: "updateStyles", payload: getChatCss()});
86-
}
87-
88-
let selectedGenerativeAiModelChange = event.affectsConfiguration("generativeAi.option");
89-
console.log("Checking if extension config changes are made to chatview.theme: ", selectedGenerativeAiModelChange);
90-
91-
if(selectedGenerativeAiModelChange) {
92-
this.currentWebView?.webview.postMessage({ type: "modelUpdate", payload: { model: getConfigValue("generativeAi.option")} });
93-
}
94-
})
9576
}
9677

9778
private async setWebviewHtml(view: vscode.WebviewView): Promise<void> {
9879
const codepatterns: FileUploader = new FileUploader(this._context);
9980
// const knowledgeBaseDocs: string[] = await codepatterns.getFiles();
10081
view.webview.html = getWebviewContent(
10182
this.currentWebView?.webview!,
102-
this._extensionUri,
83+
this._extensionUri
10384
);
10485
}
10586

10687
private async setupMessageHandler(
10788
apiKey: string,
10889
modelName: string,
109-
_view: vscode.WebviewView,
90+
_view: vscode.WebviewView
11091
): Promise<void> {
11192
try {
11293
_view.webview.onDidReceiveMessage(async (message) => {
@@ -115,7 +96,7 @@ export abstract class BaseWebViewProvider implements vscode.Disposable {
11596
if (message.tags?.length > 0) {
11697
response = await this.generateResponse(
11798
message.message,
118-
message.tags,
99+
message.tags
119100
);
120101
} else {
121102
response = await this.generateResponse(message.message);
@@ -137,12 +118,12 @@ export abstract class BaseWebViewProvider implements vscode.Disposable {
137118
}
138119
abstract generateResponse(
139120
message?: string,
140-
metaData?: Record<string, any>,
121+
metaData?: Record<string, any>
141122
): Promise<string | undefined>;
142123

143124
abstract sendResponse(
144125
response: string,
145-
currentChat?: string,
126+
currentChat?: string
146127
): Promise<boolean | undefined>;
147128

148129
public dispose(): void {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as vscode from "vscode";
2+
import { Logger } from "../infrastructure/logger/logger";
3+
4+
type Ttopic = "updateStyles" | "modelUpdate" | "bot" | "user-input";
5+
6+
interface IPostMessage {
7+
// Note. Change to topic instead
8+
type: Ttopic;
9+
payload: Record<string, any> | string;
10+
}
11+
12+
export class WebViewEventsProvider {
13+
private readonly logger: Logger;
14+
constructor(private readonly webView: vscode.WebviewView) {
15+
// Note: Use factory method for the logger
16+
this.logger = new Logger("WebViewEventsProvider");
17+
}
18+
19+
public postMessage(message: IPostMessage) {
20+
const { type, payload } = message;
21+
try {
22+
this.webView.webview.postMessage({ type, payload });
23+
} catch (error: any) {
24+
this.logger.error(`Unable to emit ${type}`, error);
25+
throw new Error(error);
26+
}
27+
}
28+
}

src/webview/chat_css.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,6 @@ div.code {
270270
align-items: center;
271271
margin-bottom: -15px;
272272
padding: 5px 10px;
273-
background-color: #222;
274273
color: #fff;
275274
border-top-left-radius: 4px;
276275
border-top-right-radius: 4px;

src/webview/chat_html.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { getUri } from "../utils/utils";
22
// import { chatCss } from "./chat_css";
3-
import { chatJs } from "./chat_js";
4-
import * as path from "path";
5-
import { Webview, Uri } from "vscode";
3+
import { Uri, Webview } from "vscode";
64

75
// This function generates a random 32-character string (nonce) using alphanumeric characters
86
// A nonce is a unique, random value used for security purposes, typically to prevent replay attacks
@@ -36,7 +34,7 @@ export const chartComponent = (webview: Webview, extensionUri: Uri) => {
3634
<html lang="en">
3735
<head>
3836
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js" integrity="sha512-EBLzUL8XLl+va/zAsmXwS7Z2B1F9HUHkZwyS/VKwh3S7T/U0nF4BaU29EP/ZSf6zgiIxYAnKLu6bJ8dqpmX5uw==" crossorigin="anonymous" referrerpolicy="no-referrer" charset="utf-8"></script>
39-
<script>hljs.highlightAll(); console.log("hljs language is: ", hljs.listLanguages());</script>
37+
<script>hljs.highlightAll();</script>
4038
<link rel="stylesheet" type="text/css" href="${stylesUri}">
4139
</head>
4240

webviewUi/src/components/webview.tsx

Lines changed: 7 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,17 @@ import {
1616
VSCodeProgressRing,
1717
VSCodeTextArea,
1818
} from "@vscode/webview-ui-toolkit/react";
19+
import type hljs from "highlight.js";
1920
import { useEffect, useState } from "react";
2021
import { codeBuddyMode, modelOptions } from "../constants/constant";
22+
import { highlightCodeBlocks } from "../utils/highlightCode";
2123
import AttachmentIcon from "./attachmentIcon";
2224
import { BotMessage } from "./botMessage";
2325
import { UserMessage } from "./personMessage";
2426
import { ModelDropdown } from "./select";
25-
import type hljs from "highlight.js";
27+
import { updateStyles } from "../utils/dynamicCss";
2628

27-
const hljsApi = window["hljs" as any] as unknown as typeof hljs
29+
const hljsApi = window["hljs" as any] as unknown as typeof hljs;
2830

2931
const vsCode = (() => {
3032
if (typeof window !== "undefined" && "acquireVsCodeApi" in window) {
@@ -44,48 +46,11 @@ export const WebviewUI = () => {
4446
const [userInput, setUserInput] = useState("");
4547
const [messages, setMessages] = useState<Message[]>([]);
4648
const [activeItem, setActiveItem] = useState<string | null>(null);
47-
const [isBotLoading, setIsBotLoading] = useState(false); // New state
48-
const [forceUpdate, setForceUpdate] = useState(0);
49-
50-
const updateStyles = (data: any) => {
51-
const existingStyle = document.getElementById("dynamic-chat-css");
52-
53-
if (existingStyle) {
54-
console.log("Removing existing extension css style.");
55-
existingStyle.remove();
56-
}
57-
58-
// Inject the new stylesheet
59-
const styleElement = document.createElement("style");
60-
styleElement.id = "dynamic-chat-css";
61-
styleElement.innerHTML = data;
62-
document.head.appendChild(styleElement);
63-
console.log("Updating extension css style.");
64-
65-
setForceUpdate(() => forceUpdate + 1);
66-
}
67-
68-
// useEffect(() => {
69-
// hljs.highlightAll(); // Ensure highlight.js runs after rendering
70-
// }, [messages]); // Runs every time messages updates
49+
const [isBotLoading, setIsBotLoading] = useState(false);
7150

7251
useEffect(() => {
73-
const highlightCodeBlocks = () => {
74-
if (!hljsApi || !messages || messages.length === 0) return;
75-
76-
document.querySelectorAll("pre code").forEach((block) => {
77-
// Auto-detect language
78-
const detected = hljsApi.highlightAuto(block.textContent || "").language;
79-
console.log("Detected language:", detected); // Debugging
80-
if(detected != undefined) {
81-
block.setHTMLUnsafe(hljsApi.highlight(block.getHTML(), {language: detected}).value);
82-
}
83-
});
84-
};
85-
86-
highlightCodeBlocks();
87-
}, [messages]); // Runs every time messages update
88-
52+
highlightCodeBlocks(hljsApi, messages);
53+
}, [messages]);
8954

9055
useEffect(() => {
9156
const messageHandler = (event: any) => {

0 commit comments

Comments
 (0)