Skip to content

Commit a712c62

Browse files
committed
Add experimental chat feature and related views and containers
1 parent 0e2f84a commit a712c62

28 files changed

+32503
-59
lines changed

images/fc.svg

+3
Loading

images/firecoder-icon.png

3.77 KB
Loading

package.json

+26-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"engines": {
1212
"vscode": "^1.84.0"
1313
},
14+
"icon": "images/firecoder-icon.png",
1415
"categories": [
1516
"Machine Learning",
1617
"Programming Languages",
@@ -31,6 +32,25 @@
3132
],
3233
"main": "./out/extension.js",
3334
"contributes": {
35+
"views": {
36+
"firecoder": [
37+
{
38+
"type": "webview",
39+
"id": "firecoder.chat-gui",
40+
"name": "",
41+
"visibility": "visible"
42+
}
43+
]
44+
},
45+
"viewsContainers": {
46+
"activitybar": [
47+
{
48+
"id": "firecoder",
49+
"title": "Firecoder Chat",
50+
"icon": "images/fc.svg"
51+
}
52+
]
53+
},
3454
"commands": [
3555
{
3656
"command": "firecoder.inlineSuggest",
@@ -67,6 +87,11 @@
6787
"default": false,
6888
"description": "Use experimental GPU Metal support for macOS."
6989
},
90+
"firecoder.experimental.chat": {
91+
"type": "boolean",
92+
"default": false,
93+
"description": "Enable experimental chat feature."
94+
},
7095
"firecoder.completion.autoMode": {
7196
"type": "string",
7297
"default": "base-small",
@@ -133,4 +158,4 @@
133158
"@grafana/faro-web-sdk": "^1.3.5",
134159
"@vscode/webview-ui-toolkit": "^1.4.0"
135160
}
136-
}
161+
}

src/common/panel/chat.ts

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { Disposable, Webview, window, Uri } from "vscode";
2+
import * as vscode from "vscode";
3+
import { getUri } from "../utils/getUri";
4+
import { getNonce } from "../utils/getNonce";
5+
6+
export class ChatPanel implements vscode.WebviewViewProvider {
7+
private disposables: Disposable[] = [];
8+
9+
constructor(private readonly extensionUri: vscode.Uri) {}
10+
11+
public resolveWebviewView(
12+
webviewView: vscode.WebviewView,
13+
context: vscode.WebviewViewResolveContext,
14+
_token: vscode.CancellationToken
15+
) {
16+
webviewView.webview.options = {
17+
enableScripts: true,
18+
localResourceRoots: [this.extensionUri],
19+
};
20+
21+
webviewView.webview.html = this.getWebviewContent(
22+
webviewView.webview,
23+
this.extensionUri
24+
);
25+
this.setWebviewMessageListener(webviewView.webview);
26+
webviewView.show();
27+
}
28+
29+
private getWebviewContent(webview: Webview, extensionUri: Uri) {
30+
const stylesUri = getUri(webview, extensionUri, [
31+
"webviews",
32+
"build",
33+
"static",
34+
"css",
35+
"main.css",
36+
]);
37+
const scriptUri = getUri(webview, extensionUri, [
38+
"webviews",
39+
"build",
40+
"static",
41+
"js",
42+
"main.js",
43+
]);
44+
45+
const nonce = getNonce();
46+
47+
return `
48+
<!DOCTYPE html>
49+
<html lang="en">
50+
<head>
51+
<meta charset="utf-8">
52+
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
53+
<meta name="theme-color" content="#000000">
54+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource}; script-src 'nonce-${nonce}';">
55+
<link rel="stylesheet" type="text/css" href="${stylesUri}">
56+
<title>FireCoder Chat</title>
57+
</head>
58+
<body>
59+
<noscript>You need to enable JavaScript to run this app.</noscript>
60+
<div id="root"></div>
61+
<script nonce="${nonce}" src="${scriptUri}"></script>
62+
</body>
63+
</html>
64+
`;
65+
}
66+
67+
private setWebviewMessageListener(webview: Webview) {
68+
webview.onDidReceiveMessage(
69+
(message: any) => {
70+
const command = message.command;
71+
const text = message.text;
72+
73+
switch (command) {
74+
case "hello":
75+
window.showInformationMessage(text);
76+
return;
77+
}
78+
},
79+
undefined,
80+
this.disposables
81+
);
82+
}
83+
}

src/common/telemetry/index.ts

+34-29
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
initializeFaro,
99
} from "@grafana/faro-core";
1010
import { FetchTransport } from "@grafana/faro-web-sdk";
11+
import Logger from "../logger";
1112

1213
type Properties = {
1314
extensionversion: string;
@@ -28,35 +29,39 @@ class FirecoderTelemetrySender implements vscode.TelemetrySender {
2829

2930
public init(context: vscode.ExtensionContext) {
3031
this.properties = this.getProperties(context);
31-
this.faro = initializeFaro({
32-
app: {
33-
name: "firecoder-vscode",
34-
version: "1.0.0",
35-
environment: "production",
36-
},
37-
transports: [
38-
new FetchTransport({
39-
url: "https://faro-collector-prod-eu-west-0.grafana.net/collect/33a834c252bb6b780b5d242def445bbd",
40-
}),
41-
],
42-
sessionTracking: {
43-
enabled: false,
44-
},
45-
dedupe: false,
46-
globalObjectKey: "firecoder-vscode",
47-
internalLoggerLevel: InternalLoggerLevel.ERROR,
48-
isolate: true,
49-
instrumentations: [],
50-
metas: [],
51-
parseStacktrace: function (err: ExtendedError): Stacktrace {
52-
return {
53-
frames: [],
54-
};
55-
},
56-
paused: false,
57-
preventGlobalExposure: false,
58-
unpatchedConsole: console,
59-
});
32+
try {
33+
this.faro = initializeFaro({
34+
app: {
35+
name: "firecoder-vscode",
36+
version: "1.0.0",
37+
environment: "production",
38+
},
39+
transports: [
40+
new FetchTransport({
41+
url: "https://faro-collector-prod-eu-west-0.grafana.net/collect/33a834c252bb6b780b5d242def445bbd",
42+
}),
43+
],
44+
sessionTracking: {
45+
enabled: false,
46+
},
47+
dedupe: false,
48+
globalObjectKey: "firecoder-vscode",
49+
internalLoggerLevel: InternalLoggerLevel.ERROR,
50+
isolate: true,
51+
instrumentations: [],
52+
metas: [],
53+
parseStacktrace: function (err: ExtendedError): Stacktrace {
54+
return {
55+
frames: [],
56+
};
57+
},
58+
paused: false,
59+
preventGlobalExposure: false,
60+
unpatchedConsole: console,
61+
});
62+
} catch (error) {
63+
Logger.error(error);
64+
}
6065
}
6166

6267
private getProperties(context: vscode.ExtensionContext) {

src/common/utils/configuration.ts

+6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ const ConfigurationProperties = {
1111
"experimental.useGpu.osx.metal": {
1212
default: false,
1313
},
14+
"experimental.chat": {
15+
default: false,
16+
},
1417
"completion.autoMode": {
1518
default: "base-small",
1619
},
@@ -30,6 +33,9 @@ interface ConfigurationPropertiesType
3033
"experimental.useGpu.osx.metal": {
3134
possibleValues: boolean;
3235
};
36+
"experimental.chat": {
37+
possibleValues: boolean;
38+
};
3339
"completion.autoMode": {
3440
possibleValues: TypeModelsBase;
3541
};

src/common/utils/getNonce.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* A helper function that returns a unique alphanumeric identifier called a nonce.
3+
*
4+
* @remarks This function is primarily used to help enforce content security
5+
* policies for resources/scripts being executed in a webview context.
6+
*
7+
* @returns A nonce
8+
*/
9+
export function getNonce() {
10+
let text = "";
11+
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
12+
for (let i = 0; i < 32; i++) {
13+
text += possible.charAt(Math.floor(Math.random() * possible.length));
14+
}
15+
return text;
16+
}

src/common/utils/getUri.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Uri, Webview } from "vscode";
2+
3+
/**
4+
* A helper function which will get the webview URI of a given file or resource.
5+
*
6+
* @remarks This URI can be used within a webview's HTML as a link to the
7+
* given file/resource.
8+
*
9+
* @param webview A reference to the extension webview
10+
* @param extensionUri The URI of the directory containing the extension
11+
* @param pathList An array of strings representing the path to a file/resource
12+
* @returns A URI pointing to the file/resource
13+
*/
14+
export function getUri(webview: Webview, extensionUri: Uri, pathList: string[]) {
15+
return webview.asWebviewUri(Uri.joinPath(extensionUri, ...pathList));
16+
}

src/extension.ts

+40-28
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { getInlineCompletionProvider } from "./common/completion";
66
import { FirecoderTelemetrySenderInstance } from "./common/telemetry";
77
import { configuration } from "./common/utils/configuration";
88
import { state } from "./common/utils/state";
9+
import { ChatPanel } from "./common/panel/chat";
910

1011
export async function activate(context: vscode.ExtensionContext) {
1112
FirecoderTelemetrySenderInstance.init(context);
@@ -18,6 +19,13 @@ export async function activate(context: vscode.ExtensionContext) {
1819
sendTelemetry: true,
1920
});
2021

22+
const provider = new ChatPanel(context.extensionUri);
23+
context.subscriptions.push(
24+
vscode.window.registerWebviewViewProvider("firecoder.chat-gui", provider, {
25+
webviewOptions: { retainContextWhenHidden: true },
26+
})
27+
);
28+
2129
context.subscriptions.push(
2230
vscode.commands.registerCommand(
2331
"firecoder.changeInlineSuggestMode",
@@ -49,40 +57,44 @@ export async function activate(context: vscode.ExtensionContext) {
4957
sendTelemetry: true,
5058
});
5159

52-
try {
53-
const serversStarted = await Promise.all(
54-
[
55-
...new Set([
56-
configuration.get("completion.autoMode"),
57-
configuration.get("completion.manuallyMode"),
58-
"chat-large" as const,
59-
]),
60-
].map((serverType) => servers[serverType].startServer())
61-
);
60+
(async () => {
61+
try {
62+
const serversStarted = await Promise.all(
63+
[
64+
...new Set([
65+
configuration.get("completion.autoMode"),
66+
configuration.get("completion.manuallyMode"),
67+
...(configuration.get("experimental.chat")
68+
? ["chat-medium" as const]
69+
: []),
70+
]),
71+
].map((serverType) => servers[serverType].startServer())
72+
);
6273

63-
if (serversStarted.some((serverStarted) => serverStarted)) {
64-
Logger.info("Server inited", {
65-
component: "main",
74+
if (serversStarted.some((serverStarted) => serverStarted)) {
75+
Logger.info("Server inited", {
76+
component: "main",
77+
sendTelemetry: true,
78+
});
79+
const InlineCompletionProvider = getInlineCompletionProvider(context);
80+
vscode.languages.registerInlineCompletionItemProvider(
81+
{ pattern: "**" },
82+
InlineCompletionProvider
83+
);
84+
}
85+
} catch (error) {
86+
vscode.window.showErrorMessage((error as Error).message);
87+
Logger.error(error, {
88+
component: "server",
6689
sendTelemetry: true,
6790
});
68-
const InlineCompletionProvider = getInlineCompletionProvider(context);
69-
vscode.languages.registerInlineCompletionItemProvider(
70-
{ pattern: "**" },
71-
InlineCompletionProvider
72-
);
7391
}
74-
} catch (error) {
75-
vscode.window.showErrorMessage((error as Error).message);
76-
Logger.error(error, {
77-
component: "server",
92+
93+
Logger.info("FireCoder is ready.", {
94+
component: "main",
7895
sendTelemetry: true,
7996
});
80-
}
81-
82-
Logger.info("FireCoder is ready.", {
83-
component: "main",
84-
sendTelemetry: true,
85-
});
97+
})();
8698
}
8799

88100
export function deactivate() {

tsconfig.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@
1212
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
1313
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
1414
// "noUnusedParameters": true, /* Report errors on unused parameters. */
15-
}
15+
},
16+
"exclude": ["node_modules", "webviews"]
1617
}

webviews/.gitignore

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# production
12+
/build
13+
14+
# misc
15+
.DS_Store
16+
.env.local
17+
.env.development.local
18+
.env.test.local
19+
.env.production.local
20+
21+
npm-debug.log*
22+
yarn-debug.log*
23+
yarn-error.log*

0 commit comments

Comments
 (0)