Skip to content

Commit cff3d81

Browse files
authored
feat(chat): Add chat history (#43)
1 parent d99c7dc commit cff3d81

File tree

26 files changed

+736
-158
lines changed

26 files changed

+736
-158
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@
3636
"view/title": [
3737
{
3838
"command": "firecoder.startNewChat",
39-
"group": "navigation@1"
39+
"group": "navigation",
40+
"when": "view === firecoder.chat-gui"
4041
}
4142
]
4243
},

src/common/panel/chat.ts

+160-26
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import * as vscode from "vscode";
33
import { getUri } from "../utils/getUri";
44
import { getNonce } from "../utils/getNonce";
55
import { chat } from "../chat";
6-
import { ChatMessage } from "../prompt/promptChat";
6+
import { Chat, ChatMessage } from "../prompt/promptChat";
7+
import { state } from "../utils/state";
78

89
export type MessageType =
910
| {
@@ -15,11 +16,43 @@ export type MessageType =
1516
| {
1617
type: "e2w-response";
1718
id: string;
18-
command: string;
1919
done: boolean;
2020
data: any;
2121
};
2222

23+
type MessageToExtention =
24+
| {
25+
type: "send-message";
26+
data: ChatMessage[];
27+
}
28+
| {
29+
type: "abort-generate";
30+
id: string;
31+
}
32+
| {
33+
type: "get-chat";
34+
chatId: string;
35+
}
36+
| {
37+
type: "save-chat";
38+
chatId: string;
39+
data: Chat;
40+
}
41+
| {
42+
type: "get-chats";
43+
}
44+
| {
45+
type: "delete-chat";
46+
chatId: string;
47+
}
48+
| {
49+
type: "delete-chats";
50+
};
51+
52+
type MessageFromWebview = MessageToExtention & {
53+
id: string;
54+
};
55+
2356
export class ChatPanel implements vscode.WebviewViewProvider {
2457
private disposables: Disposable[] = [];
2558
private webview: Webview | undefined;
@@ -50,6 +83,7 @@ export class ChatPanel implements vscode.WebviewViewProvider {
5083
"css",
5184
"main.css",
5285
]);
86+
5387
const scriptUri = getUri(webview, extensionUri, [
5488
"webviews",
5589
"build",
@@ -95,49 +129,70 @@ export class ChatPanel implements vscode.WebviewViewProvider {
95129

96130
private setWebviewMessageListener(webview: Webview) {
97131
webview.onDidReceiveMessage(
98-
async (message: any) => {
132+
async (message: MessageFromWebview) => {
99133
if (message.type in this.messageCallback) {
100134
this.messageCallback[message.type]();
101135
return;
102136
}
137+
103138
const type = message.type;
104139

105140
switch (type) {
106-
case "sendMessage":
141+
case "send-message":
107142
await this.handleStartGeneration({
143+
id: message.id,
108144
chatMessage: message.data,
109-
messageId: message.messageId,
110-
messageType: message.type,
111145
});
112-
return;
146+
break;
147+
case "get-chat":
148+
await this.handleGetChat({
149+
id: message.id,
150+
chatId: message.chatId,
151+
});
152+
break;
153+
case "save-chat":
154+
await this.handleSaveChat({
155+
id: message.id,
156+
chatId: message.chatId,
157+
history: message.data,
158+
});
159+
break;
160+
case "delete-chat":
161+
await this.handleDeleteChat({
162+
id: message.id,
163+
chatId: message.chatId,
164+
});
165+
break;
166+
case "delete-chats":
167+
await this.handleDeleteChats({
168+
id: message.id,
169+
});
170+
break;
171+
case "get-chats":
172+
await this.handleGetChats({
173+
id: message.id,
174+
});
175+
break;
176+
default:
177+
break;
113178
}
114179
},
115180
undefined,
116181
this.disposables
117182
);
118183
}
119184

120-
private addMessageListener(
121-
commandOrMessageId: string,
122-
callback: (message: any) => void
123-
) {
124-
this.messageCallback[commandOrMessageId] = callback;
125-
}
126-
127185
private async handleStartGeneration({
128-
messageId,
129-
messageType,
186+
id,
130187
chatMessage,
131188
}: {
132-
messageId: string;
133-
messageType: string;
189+
id: string;
134190
chatMessage: ChatMessage[];
135191
}) {
136-
const sendResponse = (messageToResponse: any, done: boolean) => {
192+
const sendResponse = (messageToResponse: string, done: boolean) => {
137193
this.postMessage({
138194
type: "e2w-response",
139-
id: messageId,
140-
command: messageType,
195+
id: id,
141196
data: messageToResponse,
142197
done: done,
143198
});
@@ -158,8 +213,91 @@ export class ChatPanel implements vscode.WebviewViewProvider {
158213
sendResponse("", true);
159214
}
160215

216+
private async handleGetChat({ chatId, id }: { chatId: string; id: string }) {
217+
const sendResponse = (messageToResponse: Chat | null, done: boolean) => {
218+
this.postMessage({
219+
type: "e2w-response",
220+
id: id,
221+
data: messageToResponse,
222+
done: done,
223+
});
224+
};
225+
226+
const history = state.global.get(`chat-${chatId}`);
227+
if (history) {
228+
sendResponse(history, true);
229+
} else {
230+
sendResponse(null, true);
231+
}
232+
}
233+
234+
private async handleSaveChat({
235+
chatId,
236+
history,
237+
id,
238+
}: {
239+
chatId: string;
240+
history: Chat;
241+
id: string;
242+
}) {
243+
await state.global.update(`chat-${chatId}`, history);
244+
await this.postMessage({
245+
type: "e2w-response",
246+
id: id,
247+
data: "",
248+
done: true,
249+
});
250+
}
251+
252+
private async handleDeleteChat({
253+
chatId,
254+
id,
255+
}: {
256+
chatId: string;
257+
id: string;
258+
}) {
259+
await state.global.delete(`chat-${chatId}`);
260+
await this.postMessage({
261+
type: "e2w-response",
262+
id: id,
263+
data: "",
264+
done: true,
265+
});
266+
}
267+
268+
private async handleDeleteChats({ id }: { id: string }) {
269+
await state.global.deleteChats();
270+
await this.postMessage({
271+
type: "e2w-response",
272+
id: id,
273+
data: "",
274+
done: true,
275+
});
276+
}
277+
278+
private async handleGetChats({ id }: { id: string }) {
279+
const chats = state.global.getChats();
280+
await this.postMessage({
281+
type: "e2w-response",
282+
id: id,
283+
data: chats,
284+
done: true,
285+
});
286+
}
287+
288+
private addMessageListener(
289+
commandOrMessageId: string,
290+
callback: (message: any) => void
291+
) {
292+
this.messageCallback[commandOrMessageId] = callback;
293+
}
294+
295+
private async postMessage(message: MessageType) {
296+
await this.webview?.postMessage(message);
297+
}
298+
161299
public async sendMessageToWebview(
162-
command: MessageType["command"],
300+
command: string,
163301
data: MessageType["data"]
164302
) {
165303
const message: MessageType = {
@@ -170,8 +308,4 @@ export class ChatPanel implements vscode.WebviewViewProvider {
170308
};
171309
await this.postMessage(message);
172310
}
173-
174-
private async postMessage(message: MessageType) {
175-
await this.webview?.postMessage(message);
176-
}
177311
}

src/common/prompt/promptChat.ts

+8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
export type ChatMessage = {
22
role: string;
33
content: string;
4+
// chatMessageId: string;
5+
};
6+
7+
export type Chat = {
8+
messages: ChatMessage[];
9+
chatId: string;
10+
date: number;
11+
title: string;
412
};
513

614
const promptBaseDefault = `You are an AI programming assistant, utilizing the DeepSeek Coder model, developed by DeepSeek Company, and you only answer questions related to computer science. For politically sensitive questions, security and privacy issues, and other non-computer science questions, you will refuse to answer.

src/common/utils/state.ts

+38-18
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,17 @@
11
import * as vscode from "vscode";
22
import type { Spec } from "../download";
3+
import { Chat } from "../prompt/promptChat";
34

45
const StateValues = {
5-
inlineSuggestModeAuto: {
6-
default: true,
7-
},
8-
serverSpec: {
9-
default: null,
10-
},
6+
inlineSuggestModeAuto: true,
7+
serverSpec: null,
8+
};
9+
type StateValuesType = {
10+
inlineSuggestModeAuto: boolean;
11+
serverSpec: Spec | null;
12+
[key: `chat-${string}`]: Chat | undefined;
1113
};
1214

13-
interface StateValuesType extends Record<keyof typeof StateValues, any> {
14-
inlineSuggestModeAuto: {
15-
possibleValues: boolean;
16-
};
17-
serverSpec: {
18-
possibleValues: Spec | null;
19-
};
20-
}
2115
class State {
2216
state?: vscode.Memento;
2317
constructor() {}
@@ -26,18 +20,44 @@ class State {
2620
this.state = state;
2721
}
2822

29-
public get<T extends keyof StateValuesType>(
23+
public get<T extends keyof StateValuesType & string>(
3024
key: T
31-
): StateValuesType[T]["possibleValues"] {
32-
return this.state?.get(key) ?? StateValues[key]["default"];
25+
): StateValuesType[T] {
26+
// @ts-ignore
27+
return this.state?.get(key) ?? StateValues[key];
3328
}
3429

3530
public async update<T extends keyof StateValuesType>(
3631
key: T,
37-
value: StateValuesType[T]["possibleValues"]
32+
value: StateValuesType[T]
3833
) {
3934
await this.state?.update(key, value);
4035
}
36+
37+
public getChats(): Chat[] {
38+
const allKeys = (this.state?.keys() ||
39+
[]) as unknown as (keyof StateValuesType)[];
40+
41+
return allKeys
42+
.filter((key) => key.startsWith("chat-"))
43+
.map((key) => {
44+
return this.get(key as `chat-${string}`) as Chat;
45+
});
46+
}
47+
public async delete<T extends keyof StateValuesType>(key: T) {
48+
await this.state?.update(key, undefined);
49+
}
50+
51+
public async deleteChats() {
52+
const allKeys = (this.state?.keys() ||
53+
[]) as unknown as (keyof StateValuesType)[];
54+
55+
await Promise.all(
56+
allKeys
57+
.filter((key) => key.startsWith("chat-"))
58+
.map((key) => this.delete(key as `chat-${string}`))
59+
);
60+
}
4161
}
4262

4363
export const state = {

src/extension.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export async function activate(context: vscode.ExtensionContext) {
2929
);
3030
context.subscriptions.push(
3131
vscode.commands.registerCommand("firecoder.startNewChat", async () => {
32-
await provider.sendMessageToWebview("startNewChat", {});
32+
await provider.sendMessageToWebview("start-new-chat", {});
3333
})
3434
);
3535

0 commit comments

Comments
 (0)