Skip to content

Commit

Permalink
feat: add session history (#1)
Browse files Browse the repository at this point in the history
* fix: use vscode secret storage to store model setting

* feat: add session history
  • Loading branch information
lee88688 authored Nov 13, 2024
1 parent 74b8d4e commit 3336972
Show file tree
Hide file tree
Showing 13 changed files with 265 additions and 14 deletions.
12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@
"command": "aider-composer.ConfirmModify",
"title": "Confirm Modify",
"icon": "$(check)"
},
{
"command": "aider-composer.HistoryButtonClick",
"title": "History",
"icon": "$(history)"
}
],
"menus": {
Expand All @@ -65,9 +70,14 @@
"group": "navigation@1"
},
{
"command": "aider-composer.SettingButtonClick",
"command": "aider-composer.HistoryButtonClick",
"when": "view == aider-composer.SidebarProvider",
"group": "navigation@2"
},
{
"command": "aider-composer.SettingButtonClick",
"when": "view == aider-composer.SidebarProvider",
"group": "navigation@3"
}
],
"editor/title": [
Expand Down
7 changes: 7 additions & 0 deletions server/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,13 @@ def clear():
manager.coder.cur_messages = []
return jsonify({})

@app.route('/api/chat/session', methods=['PUT'])
def set_history():
data = request.json
manager.coder.done_messages = data
manager.coder.cur_messages = []
return jsonify({})

@app.route('/api/chat/setting', methods=['POST'])
def update_setting():
data = request.json
Expand Down
8 changes: 8 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ export function activate(context: vscode.ExtensionContext) {
}),
);

// history button click
context.subscriptions.push(
vscode.commands.registerCommand('aider-composer.HistoryButtonClick', () => {
outputChannel.info('History button clicked!');
webviewProvider.setViewType('history');
}),
);

// confirm modify
context.subscriptions.push(
vscode.commands.registerCommand(
Expand Down
23 changes: 22 additions & 1 deletion src/webViewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,15 @@ class VscodeReactView implements WebviewViewProvider {
case 'delete-workspace-state':
promise = this.deleteWorkspaceState(data);
break;
case 'set-secret-state':
promise = this.setSecretState(data);
break;
case 'get-secret-state':
promise = this.getSecretState(data);
break;
case 'delete-secret-state':
promise = this.deleteSecretState(data);
break;
// Add more switch case statements here as more webview message commands
// are created within the webview context (i.e. inside media/main.js)
}
Expand All @@ -238,6 +247,18 @@ class VscodeReactView implements WebviewViewProvider {
}
}

private async setSecretState(data: { key: string; value: any }) {
return this.context.secrets.store(data.key, data.value);
}

private async getSecretState(data: { key: string }) {
return this.context.secrets.get(data.key);
}

private async deleteSecretState(data: { key: string }) {
return this.context.secrets.delete(data.key);
}

private async setWorkspaceState(data: { key: string; value: any }) {
return this.context.workspaceState.update(data.key, data.value);
}
Expand Down Expand Up @@ -385,7 +406,7 @@ class VscodeReactView implements WebviewViewProvider {
});
}

setViewType(viewType: 'chat' | 'setting') {
setViewType(viewType: 'chat' | 'setting' | 'history') {
this.postMessageToWebview({
id: nanoid(),
command: 'set-view-type',
Expand Down
4 changes: 4 additions & 0 deletions ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import './app.scss';
import { Chat } from './views/chat';
import Setting from './views/setting';
import Welcome from './views/welcome';
import History from './views/history';

function App() {
const extensionState = useExtensionStore();
Expand All @@ -19,6 +20,9 @@ function App() {
case 'welcome':
view = <Welcome />;
break;
case 'history':
view = <History />;
break;
default:
view = null;
}
Expand Down
12 changes: 12 additions & 0 deletions ui/src/commandApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,15 @@ export function getWorkspaceState(data: { key: string }) {
export function deleteWorkspaceState(data: { key: string }) {
return callCommand('delete-workspace-state', data);
}

export function setSecretState(data: { key: string; value: unknown }) {
return callCommand('set-secret-state', data);
}

export function getSecretState(data: { key: string }) {
return callCommand('get-secret-state', data);
}

export function deleteSecretState(data: { key: string }) {
return callCommand('delete-secret-state', data);
}
2 changes: 1 addition & 1 deletion ui/src/components/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function ListItem(
title={props.title}
onClick={props.onClick}
>
<div>{props.children}</div>
<div style={{ flexGrow: 1 }}>{props.children}</div>
<div className="secondary-text">{props.secondaryText}</div>
</li>
);
Expand Down
17 changes: 17 additions & 0 deletions ui/src/stores/lib.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { createJSONStorage, StateStorage } from 'zustand/middleware';
import {
deleteGlobalState,
deleteSecretState,
deleteWorkspaceState,
getGlobalState,
getSecretState,
getWorkspaceState,
setGlobalState,
setSecretState,
setWorkspaceState,
} from '../commandApi';

Expand Down Expand Up @@ -35,3 +38,17 @@ const globalStorage: StateStorage = {
};

export const persistGlobalStorage = createJSONStorage(() => globalStorage);

const secretStorage: StateStorage = {
getItem: (key: string) => {
return getSecretState({ key });
},
setItem: (key: string, value: string) => {
return setSecretState({ key, value });
},
removeItem: (key: string) => {
return deleteSecretState({ key });
},
};

export const persistSecretStorage = createJSONStorage(() => secretStorage);
95 changes: 91 additions & 4 deletions ui/src/stores/useChatStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ type ServerChatPayload = {

type ChatReferenceItemWithReadOnly = ChatReferenceItem & { readonly?: boolean };

type ChatSessionHistory = {
id: string;
title: string;
time: number;
data: ChatMessage[];
};

export const useChatSettingStore = create(
persist(
combine(
Expand All @@ -52,9 +59,61 @@ export const useChatSettingStore = create(
),
);

export const useChatSessionStore = create(
persist(
combine(
{
sessions: [] as ChatSessionHistory[],
},
(set) => ({
setSessions(nextSessions: ChatSessionHistory[]) {
set({ sessions: nextSessions });
},
addSession(id: string, data: ChatMessage[]) {
if (!id) {
console.error('id is required');
return;
}

set((state) => {
if (state.sessions.find((s) => s.id === id)) {
return {
...state,
sessions: state.sessions.map((s) =>
s.id === id ? { ...s, data, time: Date.now() } : s,
),
};
}

const title = data[0].text;
return {
...state,
sessions: [
...state.sessions,
{ id, title, time: Date.now(), data },
],
};
});
},
deleteSession(id: string) {
set((state) => ({
...state,
sessions: state.sessions.filter((s) => s.id !== id),
}));
},
}),
),
{
name: 'sessions',
storage: persistStorage,
},
),
);

export const useChatStore = create(
combine(
{
id: nanoid(),
history: [] as ChatMessage[],
// current assistant message from server
current: undefined as ChatAssistantMessage | undefined,
Expand All @@ -66,8 +125,12 @@ export const useChatStore = create(
| undefined,
},
(set, get) => ({
setHistory(nextHistory: ChatMessage[]) {
set({ history: nextHistory });
clearChat() {
set({
id: nanoid(),
history: [],
current: undefined,
});
},
setCurrentEditorReference(reference: ChatReferenceItemWithReadOnly) {
set({ currentEditorReference: reference });
Expand Down Expand Up @@ -232,6 +295,10 @@ export const useChatStore = create(
const history = state.current
? [...state.history, state.current]
: state.history;

const id = state.id;
useChatSessionStore.getState().addSession(id, history);

return {
...state,
history,
Expand All @@ -248,11 +315,11 @@ export const useChatStore = create(
});

eventSource.onerror = (event: SSEvent) => {
// todo: 应该处理current,目前只在end事件中处理
// todo: should deal with current, currently only deal with end event
console.log('error', event);
if ((event.target as SSE | null)?.readyState === EventSource.CLOSED) {
console.log('EventSource connection closed');
// 处理流结束的逻辑
// deal with end event
} else {
console.error('EventSource error:', event);
}
Expand All @@ -262,3 +329,23 @@ export const useChatStore = create(
}),
),
);

export async function setChatSession(id: string) {
const session = useChatSessionStore
.getState()
.sessions.find((s) => s.id === id);
if (!session) {
console.error('session not found');
return;
}

const { serverUrl } = useExtensionStore.getState();
await fetch(`${serverUrl}/api/chat/session`, {
method: 'PUT',
body: JSON.stringify(
session.data.map((m) => ({ role: m.type, content: m.text })),
),
});

useChatStore.setState({ id, history: session.data, current: undefined });
}
6 changes: 4 additions & 2 deletions ui/src/stores/useExtensionStore.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import { create } from 'zustand';
import { combine } from 'zustand/middleware';

export type ViewType = 'chat' | 'setting' | 'welcome' | 'history';

const useExtensionStore = create(
combine(
{
isStarted: false,
viewType: 'welcome',
viewType: 'welcome' as ViewType,
serverUrl:
import.meta.env.NODE_ENV === 'development'
? 'http://localhost:5000'
: '',
errorMessage: '',
},
(set) => ({
setViewType: (viewType: string) => set({ viewType }),
setViewType: (viewType: ViewType) => set({ viewType }),
}),
),
);
Expand Down
4 changes: 2 additions & 2 deletions ui/src/stores/useSettingStore.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { create } from 'zustand';
import { combine, persist } from 'zustand/middleware';
import useExtensionStore from './useExtensionStore';
import { persistGlobalStorage } from './lib';
import { persistSecretStorage } from './lib';

export type ChatModelSetting = {
provider: string;
Expand Down Expand Up @@ -51,7 +51,7 @@ const useSettingStore = create(
),
{
name: 'setting',
storage: persistGlobalStorage,
storage: persistSecretStorage,
onRehydrateStorage: () => {
return (_state, error) => {
if (error) {
Expand Down
Loading

0 comments on commit 3336972

Please sign in to comment.