Skip to content

Commit

Permalink
Mem0 control panel UI (#192)
Browse files Browse the repository at this point in the history
* Add config metadata for server

* wip, add interfaces, read/write config

* toggle configuration support

* remove project id set in activation

* remove unnecessary imports

* add toggle back

* inventory toggle

* wip mem0 memories panel UI

* icons

* page control

* add filtering

* Added in-place editing of memories

* add handle delete shell + remove cancel/save buttons

* fix issues

* add button

* Add key listeners for editing

* Add to homepage + shortcut

* Add card with instructions and intro when no memories

* Add batch update

* handle batch, cancel changes

* add stop event propagation for delete card

* clean up and add colours for edits

* new status bug

* align center edit status

* fix no memories card

* Remove react.

* Remove wrong keyboard event type

* filter message

* add no memories found card

* add badge

* better margins

* add messaging protocol

* fix UI bugs

* delete on erasing memory + enter

* clean up

* add undo for delete

* improvements for ui

* fetch all memories complete

* working version, pre-cleanup

* rdy for production, add redux state for memories

* camel case

* remove console logs

---------

Co-authored-by: Duke Pan <[email protected]>
  • Loading branch information
jpan8866 and Fryingpannn authored Nov 30, 2024
1 parent e5d17c7 commit 4d695bc
Show file tree
Hide file tree
Showing 21 changed files with 865 additions and 33 deletions.
7 changes: 7 additions & 0 deletions core/config/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,13 @@ export const defaultConfig: SerializedContinueConfig = {
// },
contextProviders: defaultContextProvidersVsCode,
slashCommands: defaultSlashCommandsVscode,
integrations: [
{
name: "mem0",
description: "PearAI Personalized Chat powered by Mem0",
enabled: false,
}
],
};

export const defaultCustomCommands: CustomCommand[] = [
Expand Down
29 changes: 28 additions & 1 deletion core/config/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import {
getConfigTsPath,
getContinueDotEnv,
readAllGlobalPromptFiles,
editConfigJson
} from "../util/paths.js";
import {
defaultConfig,
Expand Down Expand Up @@ -120,6 +121,11 @@ function loadSerializedConfig(
config.allowAnonymousTelemetry = true;
}

// If integrations doesn't exist in config, write it to config.json
if (!config.integrations) {
config.integrations = [];
}

if (ideSettings.remoteConfigServerUrl) {
try {
const remoteConfigJson = resolveSerializedConfig(
Expand Down Expand Up @@ -175,7 +181,7 @@ async function serializedToIntermediateConfig(
const promptFolder = initial.experimental?.promptPath;

if (loadPromptFiles) {
let promptFiles: { path: string; content: string }[] = [];
let promptFiles: { path: string; content: string } [] = [];
promptFiles = (
await Promise.all(
workspaceDirs.map((dir) =>
Expand Down Expand Up @@ -498,6 +504,7 @@ function finalToBrowserConfig(
ui: final.ui,
experimental: final.experimental,
isBetaAccess: final?.isBetaAccess,
integrations: final.integrations || []
};
}

Expand Down Expand Up @@ -592,6 +599,26 @@ function addDefaults(config: SerializedContinueConfig): void {
addDefaultCustomCommands(config);
addDefaultContextProviders(config);
addDefaultSlashCommands(config);
addDefaultIntegrations(config);
}

function addDefaultIntegrations(config: SerializedContinueConfig): void {
defaultConfig!.integrations!.forEach((defaultIntegration) => {
const integrationExists = config?.integrations?.some(
(configIntegration) =>
configIntegration.name === defaultIntegration.name
);
if (!integrationExists) {
config!.integrations!.push(defaultIntegration);
editConfigJson((configJson) => {
if (!configJson.integrations) {
configJson.integrations = [];
}
configJson.integrations.push(defaultIntegration);
return configJson;
});
}
});
}

function addDefaultModels(config: SerializedContinueConfig): void {
Expand Down
10 changes: 10 additions & 0 deletions core/config/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,13 @@ export function deleteModel(title: string) {
return config;
});
}

export function toggleIntegration(name: string) {
editConfigJson((config) => {
const integration = config!.integrations!.find((i: any) => i.name === name);
if (integration) {
integration.enabled = !integration.enabled;
}
return config;
});
}
6 changes: 5 additions & 1 deletion core/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
setupLocalMode,
} from "./config/onboarding";
import { createNewPromptFile } from "./config/promptFile";
import { addModel, addOpenAIKey, deleteModel } from "./config/util";
import { addModel, addOpenAIKey, deleteModel, toggleIntegration } from "./config/util";
import { recentlyEditedFilesCache } from "./context/retrieval/recentlyEditedFilesCache";
import { ContinueServerClient } from "./continueServer/stubs/client";
import { getAuthUrlForTokenPage } from "./control-plane/auth/index";
Expand Down Expand Up @@ -242,6 +242,10 @@ export class Core {
deleteModel(msg.data.title);
this.configHandler.reloadConfig();
});
on("config/toggleIntegration", (msg) => {
toggleIntegration(msg.data.name);
this.configHandler.reloadConfig();
});

on("config/newPromptFile", async (msg) => {
createNewPromptFile(
Expand Down
9 changes: 9 additions & 0 deletions core/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,12 @@ export interface ModelDescription {
isDefault?: boolean;
}

export interface IntegrationDescription {
name: string;
description?: string;
enabled: boolean;
}

export type EmbeddingsProviderName =
| "huggingface-tei"
| "transformers.js"
Expand Down Expand Up @@ -945,6 +951,7 @@ export interface SerializedContinueConfig {
env?: string[];
allowAnonymousTelemetry?: boolean;
models: ModelDescription[];
integrations?: IntegrationDescription[];
systemMessage?: string;
completionOptions?: BaseCompletionOptions;
requestOptions?: RequestOptions;
Expand Down Expand Up @@ -1037,6 +1044,7 @@ export interface ContinueConfig {
analytics?: AnalyticsConfig;
docs?: SiteIndexingConfig[];
isBetaAccess?: boolean;
integrations?: IntegrationDescription[];
}

export interface BrowserSerializedContinueConfig {
Expand All @@ -1056,6 +1064,7 @@ export interface BrowserSerializedContinueConfig {
experimental?: ExperimentalConfig;
analytics?: AnalyticsConfig;
isBetaAccess?: boolean;
integrations?: IntegrationDescription[];
}

export interface PearAuth {
Expand Down
34 changes: 34 additions & 0 deletions core/llm/llms/PearAIServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ import {
pruneRawPromptFromTop,
} from "./../countTokens.js";
import { PearAICredentials } from "../../pearaiServer/PearAICredentials.js";
import { editConfigJson } from "../../util/paths.js";
import { execSync } from "child_process";
import * as vscode from "vscode";



class PearAIServer extends BaseLLM {
private credentials: PearAICredentials;

Expand Down Expand Up @@ -54,9 +58,39 @@ class PearAIServer extends BaseLLM {
// no-op
}

private _getIntegrations(): any {
let integrations = {};
editConfigJson((config) => {
integrations = config.integrations || {};
return config;
});
return integrations;
}

public static _getRepoId(): string {
try {
const gitRepo = vscode.workspace.workspaceFolders?.[0];
if (gitRepo) {
// Get the root commit hash
const rootCommitHash = execSync(
"git rev-list --max-parents=0 HEAD -n 1",
{ cwd: gitRepo.uri.fsPath }
).toString().trim().substring(0, 7);
return rootCommitHash;
} // if not git initialized, id will simply be user-id (uid)
return "";
} catch (error) {
console.error("Failed to initialize project ID:", error);
console.error("Using user ID as project ID");
return "";
}
}

private _convertArgs(options: CompletionOptions): any {
return {
model: options.model,
integrations: this._getIntegrations(),
repoId: PearAIServer._getRepoId(),
frequency_penalty: options.frequencyPenalty,
presence_penalty: options.presencePenalty,
max_tokens: options.maxTokens,
Expand Down
1 change: 1 addition & 0 deletions core/protocol/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export type ToCoreFromIdeOrWebviewProtocol = {
"config/deleteModel": [{ title: string }, void];
"config/reload": [undefined, BrowserSerializedContinueConfig];
"config/listProfiles": [undefined, ProfileDescription[]];
"config/toggleIntegration": [{name: string}, void];
"context/getContextItems": [
{
name: string;
Expand Down
5 changes: 4 additions & 1 deletion core/protocol/ideWebview.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AiderState } from "../../extensions/vscode/src/integrations/aider/types/aiderTypes.js";
import { ToolType } from "../../extensions/vscode/src/util/integrationUtils.js";
import { ToolType, Memory, MemoryChange } from "../../extensions/vscode/src/util/integrationUtils.js";
import type { RangeInFileWithContents } from "../commands/util.js";
import type { ContextSubmenuItem } from "../index.js";
import { ToIdeFromWebviewOrCoreProtocol } from "./ide.js";
Expand Down Expand Up @@ -58,6 +58,8 @@ export type ToIdeFromWebviewProtocol = ToIdeFromWebviewOrCoreProtocol & {
openInventoryHome: [undefined, void];
getUrlTitle: [string, string];
pearAIinstallation: [{tools: ToolType[], installExtensions: boolean}, void];
"mem0/getMemories": [undefined, Memory[]];
"mem0/updateMemories": [{ changes: MemoryChange[] }, boolean];
};

export type ToWebviewFromIdeProtocol = ToWebviewFromIdeOrCoreProtocol & {
Expand Down Expand Up @@ -101,6 +103,7 @@ export type ToWebviewFromIdeProtocol = ToWebviewFromIdeOrCoreProtocol & {
addPerplexityContextinChat: [{ text: string, language: string }, void];
navigateToCreator: [undefined, void];
navigateToSearch: [undefined, void];
navigateToMem0: [undefined, void];
toggleOverlay: [undefined, void];
navigateToInventoryHome: [undefined, void];
getCurrentTab: [undefined, string];
Expand Down
1 change: 1 addition & 0 deletions core/protocol/passThrough.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const WEBVIEW_TO_CORE_PASS_THROUGH: (keyof ToCoreFromWebviewProtocol)[] =
"config/getSerializedProfileInfo",
"config/deleteModel",
"config/reload",
"config/toggleIntegration",
"context/getContextItems",
"context/loadSubmenuItems",
"context/addDocs",
Expand Down
11 changes: 11 additions & 0 deletions extensions/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,12 @@
"title": "Toggle PearAI Search",
"group": "PearAI"
},
{
"command": "pearai.toggleMem0",
"category": "PearAI",
"title": "Toggle PearAI Memory",
"group": "PearAI"
},
{
"command": "pearai.toggleOverlay",
"category": "PearAI",
Expand Down Expand Up @@ -375,6 +381,11 @@
"mac": "cmd+3",
"key": "ctrl+3"
},
{
"command": "pearai.toggleMem0",
"mac": "cmd+4",
"key": "ctrl+4"
},
{
"command": "pearai.focusContinueInput",
"mac": "cmd+l",
Expand Down
3 changes: 1 addition & 2 deletions extensions/vscode/src/activation/activate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import * as vscode from "vscode";
import { VsCodeExtension } from "../extension/VsCodeExtension";
import registerQuickFixProvider from "../lang-server/codeActions";
import { getExtensionVersion } from "../util/util";
import { getExtensionUri } from "../util/vscode";
import { VsCodeContinueApi } from "./api";
import { setupInlineTips } from "./inlineTips";
import { isFirstLaunch } from "../copySettings";


export async function isVSCodeExtensionInstalled(extensionId: string): Promise<boolean> {
return vscode.extensions.getExtension(extensionId) !== undefined;
};
Expand Down Expand Up @@ -50,7 +50,6 @@ export async function attemptUninstallExtension(extensionId: string): Promise<vo
}
}


export async function activateExtension(context: vscode.ExtensionContext) {
// Add necessary files
getTsConfigPath();
Expand Down
3 changes: 3 additions & 0 deletions extensions/vscode/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,9 @@ const commandsMap: (
"pearai.toggleSearch": async () => {
await handleIntegrationShortcutKey("navigateToSearch", "perplexityMode", sidebar, [PEAR_OVERLAY_VIEW_ID]);
},
"pearai.toggleMem0": async () => {
await handleIntegrationShortcutKey("navigateToMem0", "mem0Mode", sidebar, [PEAR_OVERLAY_VIEW_ID]);
},
"pearai.toggleOverlay": async () => {
await handleIntegrationShortcutKey("toggleOverlay", "inventory", sidebar, [PEAR_OVERLAY_VIEW_ID, PEAR_CONTINUE_VIEW_ID]);
},
Expand Down
11 changes: 11 additions & 0 deletions extensions/vscode/src/extension/VsCodeMessenger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ import { getExtensionUri } from "../util/vscode";
import { VsCodeWebviewProtocol } from "../webviewProtocol";
import { attemptInstallExtension, attemptUninstallExtension, isVSCodeExtensionInstalled } from "../activation/activate";
import { checkAiderInstallation } from "../integrations/aider/aiderUtil";
import { getMem0Memories, updateMem0Memories } from "../integrations/mem0/mem0Service";
import { TOOL_COMMANDS, ToolType } from "../util/integrationUtils";
import PearAIServer from "core/llm/llms/PearAIServer";

/**
* A shared messenger class between Core and Webview
Expand Down Expand Up @@ -121,6 +123,15 @@ export class VsCodeMessenger {
console.log("Aider installation status:", isAiderInstalled);
return isAiderInstalled;
});
this.onWebview("mem0/getMemories", async (msg) => {

const memories = await getMem0Memories(PearAIServer._getRepoId());
return memories;
});
this.onWebview("mem0/updateMemories", async (msg) => {
const response = await updateMem0Memories(PearAIServer._getRepoId(), msg.data.changes);
return response;
});
this.onWebview("is_vscode_extension_installed", async (msg) => {
const isInstalled = await isVSCodeExtensionInstalled(msg.data.extensionId);
console.log("VSCode extension installation status:", isInstalled);
Expand Down
50 changes: 50 additions & 0 deletions extensions/vscode/src/integrations/mem0/mem0Service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { SERVER_URL } from "core/util/parameters";
import { getHeaders } from "core/pearaiServer/stubs/headers";
import { MemoryChange } from "../../util/integrationUtils";
import * as vscode from 'vscode';

export async function getMem0Memories(repo_id: string) {
try {
const baseHeaders = await getHeaders();
const auth: any = await vscode.commands.executeCommand("pearai.getPearAuth");
const response = await fetch(`${SERVER_URL}/integrations/memory/${repo_id}`, {
method: "GET",
headers: {
...baseHeaders,
"Content-Type": "application/json",
Authorization: `Bearer ${auth.accessToken}`,
},
});

if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.detail || `${response.statusText}`);
}

const data = await response.json();
return data;
} catch (error) {
// Show error message in VSCode
vscode.window.showErrorMessage(`Error fetching memories: ${(error as any).message}`);
}
}

export async function updateMem0Memories(repo_id: string, changes: MemoryChange[]) {
const baseHeaders = await getHeaders();
const auth: any = await vscode.commands.executeCommand("pearai.getPearAuth");

const response = await fetch(`${SERVER_URL}/integrations/memory/update`, {
method: "POST",
headers: {
...baseHeaders,
"Content-Type": "application/json",
Authorization: `Bearer ${auth.accessToken}`,
},
body: JSON.stringify({
id: repo_id,
updatedMemories: changes,
}),
});
return await response.json();
}
Loading

0 comments on commit 4d695bc

Please sign in to comment.