Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cody: Improve chat history #52904

Merged
merged 14 commits into from
Jun 12, 2023
Merged
5 changes: 5 additions & 0 deletions client/cody-ui/src/chat/Transcript.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ export const Transcript: React.FunctionComponent<
top: transcriptContainerRef.current.scrollHeight,
})
}

// scroll to the end when messages are loaded
transcriptContainerRef.current.scrollTo({
top: transcriptContainerRef.current.scrollHeight,
})
}
}, [transcript, transcriptContainerRef])

Expand Down
33 changes: 30 additions & 3 deletions client/cody/src/chat/ChatViewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,15 +192,13 @@ export class ChatViewProvider implements vscode.WebviewViewProvider, vscode.Disp

/**
* Restores a session from a chatID
* We delete the loaded session from our in-memory chatHistory (to hide it from the history view)
* but don't modify the localStorage as no data changes when a session is restored
*/
public async restoreSession(chatID: string): Promise<void> {
await this.saveTranscriptToChatHistory()
this.cancelCompletion()
this.currentChatID = chatID
this.transcript = Transcript.fromJSON(this.chatHistory[chatID])
delete this.chatHistory[chatID]
await this.transcript.toJSON()
this.sendTranscript()
this.sendChatHistory()
}
Expand All @@ -214,6 +212,7 @@ export class ChatViewProvider implements vscode.WebviewViewProvider, vscode.Disp
this.publishConfig()
this.sendTranscript()
this.sendChatHistory()
await this.loadRecentChat()
break
case 'submit':
await this.onHumanMessageSubmitted(message.text, message.submitType)
Expand Down Expand Up @@ -256,6 +255,9 @@ export class ChatViewProvider implements vscode.WebviewViewProvider, vscode.Disp
case 'restoreHistory':
await this.restoreSession(message.chatID)
break
case 'deleteHistory':
await this.deleteHistory(message.chatID)
break
case 'links':
void this.openExternalLinks(message.value)
break
Expand Down Expand Up @@ -374,6 +376,7 @@ export class ChatViewProvider implements vscode.WebviewViewProvider, vscode.Disp
this.cancelCompletionCallback = null
this.sendTranscript()
void this.saveTranscriptToChatHistory()
this.sendChatHistory()
abeatrix marked this conversation as resolved.
Show resolved Hide resolved
void vscode.commands.executeCommand('setContext', 'cody.reply.pending', false)
this.logEmbeddingsSearchErrors()
}
Expand Down Expand Up @@ -623,6 +626,15 @@ export class ChatViewProvider implements vscode.WebviewViewProvider, vscode.Disp
this.setWebviewView('login')
}

/**
* Delete history from current chat history and local storage
*/
private async deleteHistory(chatID: string): Promise<void> {
delete this.chatHistory[chatID]
await this.localStorage.deleteChatHistory(chatID)
abeatrix marked this conversation as resolved.
Show resolved Hide resolved
this.sendChatHistory()
}

/**
* Loads chat history from local storage
*/
Expand All @@ -634,6 +646,21 @@ export class ChatViewProvider implements vscode.WebviewViewProvider, vscode.Disp
}
}

/**
* Loads the most recent chat
*/
private async loadRecentChat(): Promise<void> {
const localHistory = this.localStorage.getChatHistory()
if (localHistory) {
const chats = localHistory.chat
const sortedChats = Object.entries(chats).sort(
(a, b) => +new Date(b[1].lastInteractionTimestamp) - +new Date(a[1].lastInteractionTimestamp)
)
const chatID = sortedChats[0][0]
await this.restoreSession(chatID)
}
}

/**
* Sends chat history to webview
*/
Expand Down
1 change: 1 addition & 0 deletions client/cody/src/chat/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export type WebviewMessage =
| { command: 'removeToken' }
| { command: 'removeHistory' }
| { command: 'restoreHistory'; chatID: string }
| { command: 'deleteHistory'; chatID: string }
| { command: 'links'; value: string }
| { command: 'openFile'; filePath: string }
| { command: 'edit'; text: string }
Expand Down
12 changes: 12 additions & 0 deletions client/cody/src/services/LocalStorageProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,18 @@ export class LocalStorage {
}
}

public async deleteChatHistory(chatID: string): Promise<void> {
const userHistory = this.getChatHistory()
if (userHistory) {
try {
delete userHistory.chat[chatID]
await this.storage.update(this.KEY_LOCAL_HISTORY, { ...userHistory })
} catch (error) {
console.error(error)
}
}
}

public async removeChatHistory(): Promise<void> {
try {
await this.storage.update(this.KEY_LOCAL_HISTORY, null)
Expand Down
11 changes: 11 additions & 0 deletions client/cody/test/e2e/history.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ test('checks for the chat history and new session', async ({ page, sidebar }) =>
await page.click('[aria-label="Cody: Chat History"]')
await expect(sidebar.getByText('Chat History')).toBeVisible()

// start a new chat session and check history

await page.click('[aria-label="Cody: Start a New Chat Session"]')
await expect(sidebar.getByText("Hello! I'm Cody. I can write code and answer questions for you.")).toBeVisible()

await sidebar.getByRole('textbox', { name: 'Text area' }).fill('Hello')
await sidebar.locator('vscode-button').getByRole('img').click()
await expect(sidebar.getByText('Hello')).toBeVisible()
await page.getByRole('button', { name: 'Cody: Chat History' }).click()
await sidebar.locator('vscode-button').filter({ hasText: 'Hello' }).click()
await page.getByRole('button', { name: 'Cody: Chat History' }).click()
await sidebar.locator('vscode-button').filter({ hasText: 'Hello' }).locator('i').click()
await expect(sidebar.getByText('Hello')).not.toBeVisible()
})
12 changes: 12 additions & 0 deletions client/cody/webviews/UserHistory.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

.item-button {
display: block;
position: relative;
padding-top: .5rem;
padding-bottom: .5rem;
}
Expand All @@ -37,6 +38,17 @@
margin-bottom: .25rem;
}

.item-delete {
position: absolute;
right: 0.25rem;
top: 0.25rem;
abeatrix marked this conversation as resolved.
Show resolved Hide resolved
abeatrix marked this conversation as resolved.
Show resolved Hide resolved
visibility: hidden;
}

.item-button:hover .item-delete {
visibility: visible;
}

.item-last-message {
/* Show a few lines of text and then an ellipsis */
display: -webkit-box;
Expand Down
28 changes: 25 additions & 3 deletions client/cody/webviews/UserHistory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,24 @@ export const UserHistory: React.FunctionComponent<React.PropsWithChildren<Histor
setView,
vscodeAPI,
}) => {
const onDeleteHistoryClick = useCallback(
abeatrix marked this conversation as resolved.
Show resolved Hide resolved
(chatID: string): void => {
abeatrix marked this conversation as resolved.
Show resolved Hide resolved
if (userHistory) {
delete userHistory[chatID]
setUserHistory({ ...userHistory })
vscodeAPI.postMessage({ command: 'deleteHistory', chatID })
}
},
[userHistory, setUserHistory, vscodeAPI]
)

const onRemoveHistoryClick = useCallback(() => {
if (userHistory) {
vscodeAPI.postMessage({ command: 'removeHistory' })
setUserHistory(null)
setInputHistory([])
}
}, [setInputHistory, setUserHistory, userHistory, vscodeAPI])
}, [setInputHistory, userHistory, setUserHistory, vscodeAPI])

function restoreMetadata(chatID: string): void {
vscodeAPI.postMessage({ command: 'restoreHistory', chatID })
Expand Down Expand Up @@ -64,8 +75,7 @@ export const UserHistory: React.FunctionComponent<React.PropsWithChildren<Histor
+new Date(b[1].lastInteractionTimestamp) - +new Date(a[1].lastInteractionTimestamp)
)
.map(chat => {
const lastMessage =
chat[1].interactions[chat[1].interactions.length - 1].assistantMessage
const lastMessage = chat[1].interactions[chat[1].interactions.length - 1].humanMessage
if (!lastMessage?.displayText) {
return null
}
Expand All @@ -79,6 +89,18 @@ export const UserHistory: React.FunctionComponent<React.PropsWithChildren<Histor
>
<div className={styles.itemButtonInnerContainer}>
<div className={styles.itemDate}>{new Date(chat[0]).toLocaleString()}</div>
<div className={styles.itemDelete}>
<VSCodeButton
appearance="icon"
type="button"
onClick={event => {
abeatrix marked this conversation as resolved.
Show resolved Hide resolved
onDeleteHistoryClick(chat[0])
event.stopPropagation()
}}
>
<i className="codicon codicon-trash" />
</VSCodeButton>
</div>
<div className={styles.itemLastMessage}>{lastMessage.displayText}</div>
</div>
</VSCodeButton>
Expand Down
Loading