Skip to content

Commit

Permalink
Added saving chat history to the Shell (#662)
Browse files Browse the repository at this point in the history
The shell can now save chat history upon restart. 


![image](https://github.com/user-attachments/assets/1fc8fed8-b6c1-4e64-bc12-348b2841bae1)
  • Loading branch information
robgruen authored Feb 4, 2025
1 parent 7901207 commit 0a1eb2b
Show file tree
Hide file tree
Showing 13 changed files with 236 additions and 150 deletions.
125 changes: 0 additions & 125 deletions .github/workflows/build-ts-pull-request-target.yml

This file was deleted.

20 changes: 1 addition & 19 deletions ts/packages/api/src/typeAgentServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,37 +232,19 @@ export class TypeAgentServer {
const debounceCount: number =
this.fileWriteDebouncer.get(fileName)!!;
if (debounceCount == 0) {
// blob storage specific starts here
/*
const blobServiceClient = new BlobServiceClient(
this.accountURL,
new DefaultAzureCredential(),
);
const containerClient: ContainerClient =
blobServiceClient.getContainerClient(this.containerName!!);
let blobName = fileName.replace(getUserDataDir(), "");
// Create blob client from container client
const blockBlobClient: BlockBlobClient =
containerClient.getBlockBlobClient(blobName!!);
*/

try {
const localPath: string = path.join(
getUserDataDir(),
fileName,
);
// await blockBlobClient.uploadFile(localPath);

if (!this.storageProvider) {
console.log(
`Failed to upload ${fileName} to provider, no storage provider found`,
);
return;
}
await this.storageProvider.uploadFile(localPath, fileName);
// console.log(`Done uploading ${fileName} to ${blobName}`);
} catch (e) {
console.log(e);
}
Expand Down
54 changes: 48 additions & 6 deletions ts/packages/shell/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
} from "default-agent-provider";
import { ShellSettings } from "./shellSettings.js";
import { unlinkSync } from "fs";
import { existsSync } from "node:fs";
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { shellAgentProvider } from "./agent.js";
import { BrowserAgentIpc } from "./browserIpc.js";
import { WebSocketMessageV2 } from "common-utils";
Expand Down Expand Up @@ -59,6 +59,13 @@ process.argv.forEach((arg) => {
}
});

export function runningTests(): boolean {
return (
process.env["INSTANCE_NAME"] !== undefined &&
process.env["INSTANCE_NAME"].startsWith("test_") === true
);
}

let mainWindow: BrowserWindow | null = null;
let inlineWebContentView: WebContentsView | null = null;
let chatView: WebContentsView | null = null;
Expand Down Expand Up @@ -614,6 +621,24 @@ async function initialize() {
// Send settings asap
ShellSettings.getinstance().onSettingsChanged!();

// Load chat history if enabled
const chatHistory: string = path.join(
getInstanceDir(),
"chat_history.html",
);
if (
ShellSettings.getinstance().chatHistory &&
existsSync(chatHistory)
) {
chatView?.webContents.send(
"chat-history",
readFileSync(
path.join(getInstanceDir(), "chat_history.html"),
"utf-8",
),
);
}

// make sure links are opened in the external browser
mainWindow.webContents.setWindowOpenHandler((details) => {
require("electron").shell.openExternal(details.url);
Expand All @@ -623,11 +648,31 @@ async function initialize() {
// The dispatcher can be use now that dom is ready and the client is ready to receive messages
const dispatcher = await dispatcherP;
updateSummary(dispatcher);

// send the agent greeting if it's turned on
if (ShellSettings.getinstance().agentGreeting) {
dispatcher.processCommand("@greeting", "agent-0", []);
}
});

// Store the chat history whenever the DOM changes
// this let's us rehydrate the chat when reopening the shell
ipcMain.on("dom changed", async (_event, html) => {
// store the modified DOM contents
const file: string = path.join(getInstanceDir(), "chat_history.html");

debugShell(`Saving chat history to '${file}'.`, performance.now());

try {
writeFileSync(file, html);
} catch (e) {
debugShell(
`Unable to save history to '${file}'. Error: ${e}`,
performance.now(),
);
}
});

ipcMain.handle("get-localWhisper-status", async () => {
return isLocalWhisperEnabled();
});
Expand All @@ -642,6 +687,7 @@ async function initialize() {
ShellSettings.getinstance().partialCompletion =
settings.partialCompletion;
ShellSettings.getinstance().darkMode = settings.darkMode;
ShellSettings.getinstance().chatHistory = settings.chatHistory;

// write the settings to disk
ShellSettings.getinstance().save();
Expand Down Expand Up @@ -673,11 +719,7 @@ async function initialize() {
// On windows, we will spin up a local end point that listens
// for pen events which will trigger speech reco
// Don't spin this up during testing
if (
process.platform == "win32" &&
(process.env["INSTANCE_NAME"] == undefined ||
process.env["INSTANCE_NAME"].startsWith("test_") == false)
) {
if (process.platform == "win32" && !runningTests()) {
const pipePath = path.join("\\\\.\\pipe\\TypeAgent", "speech");
const server = net.createServer((stream) => {
stream.on("data", (c) => {
Expand Down
2 changes: 2 additions & 0 deletions ts/packages/shell/src/main/shellSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export class ShellSettings
public onOpenInlineBrowser: ((targetUrl: URL) => void) | null;
public onCloseInlineBrowser: EmptyFunction | null;
public darkMode: boolean;
public chatHistory: boolean;

public get width(): number {
return this.size[0] ?? defaultSettings.size[0];
Expand Down Expand Up @@ -91,6 +92,7 @@ export class ShellSettings
this.onOpenInlineBrowser = null;
this.onCloseInlineBrowser = null;
this.darkMode = settings.darkMode;
this.chatHistory = settings.chatHistory;
}

public static get filePath(): string {
Expand Down
2 changes: 2 additions & 0 deletions ts/packages/shell/src/main/shellSettingsType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type ShellSettingsType = {
partialCompletion: boolean;
disallowedDisplayType: DisplayType[];
darkMode: boolean;
chatHistory: boolean;
};

export const defaultSettings: ShellSettingsType = {
Expand All @@ -38,4 +39,5 @@ export const defaultSettings: ShellSettingsType = {
partialCompletion: true,
disallowedDisplayType: [],
darkMode: false,
chatHistory: true,
};
3 changes: 3 additions & 0 deletions ts/packages/shell/src/preload/electronTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ export interface ClientAPI {
settings: ShellSettings,
) => void,
): void;
onChatHistory(
callback: (e: Electron.IpcRendererEvent, chatHistory: string) => void,
): void;
registerClientIO(clientIO: ClientIO);
}

Expand Down
3 changes: 3 additions & 0 deletions ts/packages/shell/src/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ const api: ClientAPI = {
onSettingsChanged(callback) {
ipcRenderer.on("settings-changed", callback);
},
onChatHistory(callback) {
ipcRenderer.on("chat-history", callback);
},
registerClientIO: (clientIO: ClientIO) => {
if (clientIORegistered) {
throw new Error("ClientIO already registered");
Expand Down
34 changes: 34 additions & 0 deletions ts/packages/shell/src/renderer/assets/styles.less
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,40 @@ body {
.action-text {
color: lightgray;
}

.chat-separator-line {
background-color: gray;
}

.chat-separator-text {
color: gray;
}
}

.history {
filter: grayscale(100%);
}

.chat-separator {
display: flex;
flex-wrap: nowrap;
align-items: center;
text-align: center;
font-size: 10px;
margin: 20px 20px;
}

.chat-separator-line {
flex-grow: 1;
width: 40px;
background-color: darkgray;
height: 2px;
}

.chat-separator-text {
margin: 0px 10px;
color: darkgray;
height: 20px;
}

.wrapper {
Expand Down
3 changes: 3 additions & 0 deletions ts/packages/shell/src/renderer/src/chatView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,9 @@ export class ChatView {
getMessageElm() {
return this.topDiv;
}
getScollContainer() {
return this.messageDiv;
}

async showInputText(message: string) {
const input = this.inputContainer.querySelector(
Expand Down
Loading

0 comments on commit 0a1eb2b

Please sign in to comment.