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

Chat: Edit button to rename history chats #1818

Merged
merged 22 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions lib/shared/src/chat/transcript/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface TranscriptJSON {
// This is the timestamp of the first interaction.
id: string
chatModel?: string
chatTitle?: string
interactions: InteractionJSON[]
lastInteractionTimestamp: string
scope?: TranscriptJSONScope
Expand Down Expand Up @@ -65,7 +66,8 @@ export class Transcript {
}
),
json.id,
json.chatModel
json.chatModel,
json.chatTitle
)
}

Expand All @@ -75,13 +77,16 @@ export class Transcript {

public chatModel: string | undefined = undefined

constructor(interactions: Interaction[] = [], id?: string, chatModel?: string) {
public chatTitle: string | undefined = undefined

constructor(interactions: Interaction[] = [], id?: string, chatModel?: string, title?: string) {
this.interactions = interactions
this.internalID =
id ||
this.interactions.find(({ timestamp }) => !isNaN(new Date(timestamp) as any))?.timestamp ||
new Date().toISOString()
this.chatModel = chatModel
this.chatTitle = title || this.getLastInteraction()?.getHumanMessage()?.displayText
}

public get id(): string {
Expand All @@ -96,6 +101,10 @@ export class Transcript {
this.chatModel = chatModel
}

public setChatTitle(title: string): void {
this.chatTitle = title
}

public get isEmpty(): boolean {
return this.interactions.length === 0
}
Expand Down Expand Up @@ -235,6 +244,7 @@ export class Transcript {
return {
id: this.id,
chatModel: this.chatModel,
chatTitle: this.chatTitle,
interactions,
lastInteractionTimestamp: this.lastInteractionTimestamp,
scope: scope
Expand All @@ -251,6 +261,7 @@ export class Transcript {
return {
id: this.id,
chatModel: this.chatModel,
chatTitle: this.chatTitle,
interactions: [],
lastInteractionTimestamp: this.lastInteractionTimestamp,
scope: scope
Expand Down
1 change: 1 addition & 0 deletions vscode/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ This is a log of all notable changes to Cody for VS Code. [Unreleased] changes a
- Chat: @'ing files now uses a case insensitive fuzzy search. [pull/1889](https://github.com/sourcegraph/cody/pull/1889)
- Edit: Added a faster, more optimized response for the "document" command. [pull/1900](https://github.com/sourcegraph/cody/pull/1900)
- Chat: Restore last opened chat panel on reload. [pull/1918](https://github.com/sourcegraph/cody/pull/1918)
- Chat: Edit button to rename the chat history. [pull/1818](https://github.com/sourcegraph/cody/pull/1818)

### Fixed

Expand Down
15 changes: 14 additions & 1 deletion vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,14 @@
"group": "Cody",
"icon": "$(layout-sidebar-left)"
},
{
"command": "cody.chat.history.edit",
"category": "Cody",
"title": "Rename Chat",
"group": "Cody",
"icon": "$(edit)",
"when": "cody.activated && cody.hasChatHistory"
},
{
"command": "cody.chat.history.clear",
"category": "Cody",
Expand Down Expand Up @@ -701,10 +709,15 @@
}
],
"view/item/context": [
{
"command": "cody.chat.history.edit",
"when": "view == cody.chat.tree.view && cody.activated && cody.hasChatHistory",
"group": "inline@1"
},
{
"command": "cody.chat.history.delete",
"when": "view == cody.chat.tree.view && cody.activated && cody.hasChatHistory",
"group": "inline"
"group": "inline@2"
}
]
},
Expand Down
2 changes: 2 additions & 0 deletions vscode/src/chat/MessageProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export abstract class MessageProvider extends MessageHandler implements vscode.D
protected platform: Pick<PlatformContext, 'recipes'>

protected chatModel: string | undefined = undefined
protected chatTitle: string | undefined = 'Untitled'

constructor(options: MessageProviderOptions) {
super()
Expand Down Expand Up @@ -171,6 +172,7 @@ export abstract class MessageProvider extends MessageHandler implements vscode.D
this.createNewChatID(chatID)
this.transcript = Transcript.fromJSON(history)
this.chatModel = this.transcript.chatModel
this.chatTitle = chatHistory.getChat(chatID)?.chatTitle
await this.transcript.toJSON()
this.sendTranscript()
this.sendHistory()
Expand Down
9 changes: 9 additions & 0 deletions vscode/src/chat/chat-view/ChatManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export class ChatManager implements vscode.Disposable {
vscode.commands.registerCommand('cody.chat.history.export', async () => this.exportHistory()),
vscode.commands.registerCommand('cody.chat.history.clear', async () => this.clearHistory()),
vscode.commands.registerCommand('cody.chat.history.delete', async item => this.clearHistory(item)),
vscode.commands.registerCommand('cody.chat.history.edit', async item => this.editChatHistory(item)),
vscode.commands.registerCommand('cody.chat.panel.new', async () => this.createNewWebviewPanel()),
vscode.commands.registerCommand('cody.chat.panel.restore', (id, chat) => this.restorePanel(id, chat)),
vscode.commands.registerCommand('cody.chat.open.file', async fsPath => this.openFileFromChat(fsPath))
Expand Down Expand Up @@ -127,6 +128,14 @@ export class ChatManager implements vscode.Disposable {
await chatProvider.executeCustomCommand(title, type)
}

public async editChatHistory(treeItem?: vscode.TreeItem): Promise<void> {
const chatID = treeItem?.id
abeatrix marked this conversation as resolved.
Show resolved Hide resolved
const chatLabel = treeItem?.label as vscode.TreeItemLabel
if (chatID) {
await this.chatPanelsManager?.editChatHistory(chatID, chatLabel.label)
}
}

public async clearHistory(treeItem?: vscode.TreeItem): Promise<void> {
const chatID = treeItem?.id
if (chatID) {
Expand Down
22 changes: 21 additions & 1 deletion vscode/src/chat/chat-view/ChatPanelProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,13 +234,26 @@ export class ChatPanelProvider extends MessageProvider {
chatID: this.sessionID,
})

const currentTitle = chatHistory.getChat(this.sessionID)?.chatTitle || this.transcript.chatTitle
if (currentTitle) {
this.handleChatTitle(currentTitle)
return
}
// Update / reset webview panel title
const text = this.transcript.getLastInteraction()?.getHumanMessage()?.displayText || 'New Chat'
if (this.webviewPanel) {
this.webviewPanel.title = getChatPanelTitle(text)
}
}

public handleChatTitle(title: string): void {
this.chatTitle = title
this.transcript.setChatTitle(title)
if (this.webviewPanel) {
this.webviewPanel.title = title
}
}

/**
* Send transcript error to webview
*/
Expand Down Expand Up @@ -395,8 +408,10 @@ export class ChatPanelProvider extends MessageProvider {

this.startUpChatID = chatID

const chatTitle = chatID ? chatHistory.getChat(chatID)?.chatTitle : lastQuestion

const viewType = CodyChatPanelViewType
const panelTitle = getChatPanelTitle(lastQuestion)
const panelTitle = chatTitle || getChatPanelTitle(lastQuestion)
const viewColumn = activePanelViewColumn || vscode.ViewColumn.Beside
const webviewPath = vscode.Uri.joinPath(this.extensionUri, 'dist', 'webviews')
const panel = vscode.window.createWebviewPanel(
Expand All @@ -422,6 +437,11 @@ export class ChatPanelProvider extends MessageProvider {
public async revive(webviewPanel: vscode.WebviewPanel, chatID: string): Promise<void> {
logDebug('ChatPanelProvider:revive', 'reviving webview panel')
this.startUpChatID = chatID
const title = chatHistory.getChat(chatID)?.chatTitle
if (chatID && title) {
this.chatTitle = title
webviewPanel.title = title
}
await this.registerWebviewPanel(webviewPanel)
}

Expand Down
21 changes: 21 additions & 0 deletions vscode/src/chat/chat-view/ChatPanelsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { TreeViewProvider } from '../../services/TreeViewProvider'
import { CachedRemoteEmbeddingsClient } from '../CachedRemoteEmbeddingsClient'
import { AuthStatus } from '../protocol'

import { chatHistory } from './ChatHistoryManager'
import { CodyChatPanelViewType } from './ChatManager'
import { ChatPanelProvider, ChatPanelProviderOptions, ChatViewProviderWebview } from './ChatPanelProvider'
import { SidebarChatOptions } from './SidebarChatProvider'
Expand All @@ -35,6 +36,7 @@ export interface IChatPanelProvider extends vscode.Disposable {
executeCustomCommand(title: string, type?: CustomCommandType): Promise<void>
clearAndRestartSession(): Promise<void>
clearChatHistory(chatID: ChatID): Promise<void>
handleChatTitle(title: string): void
triggerNotice(notice: { key: string }): void
webviewPanel?: vscode.WebviewPanel
webview?: ChatViewProviderWebview
Expand Down Expand Up @@ -250,6 +252,25 @@ export class ChatPanelsManager implements vscode.Disposable {
await this.treeViewProvider.updateTree(createCodyChatTreeItems())
}

public async editChatHistory(chatID: string, label: string): Promise<void> {
await vscode.window
.showInputBox({
prompt: 'Enter new chat name',
value: label,
})
.then(async title => {
const history = chatHistory.getChat(chatID)
if (title && history) {
history.chatTitle = title
await chatHistory.saveChat(history)
await this.updateTreeViewHistory()
const chatIDUTC = new Date(chatID).toUTCString()
const provider = this.panelProvidersMap.get(chatID) || this.panelProvidersMap.get(chatIDUTC)
provider?.handleChatTitle(title)
}
})
}

public async clearHistory(chatID?: string): Promise<void> {
if (chatID) {
this.disposeProvider(chatID)
Expand Down
8 changes: 7 additions & 1 deletion vscode/src/chat/chat-view/SimpleChatModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ export class SimpleChatModel {
constructor(
public modelID: string,
private messagesWithContext: MessageWithContext[] = [],
public readonly sessionID: string = new Date(Date.now()).toUTCString()
public readonly sessionID: string = new Date(Date.now()).toUTCString(),
public chatTitle?: string
) {}

public isEmpty(): boolean {
Expand Down Expand Up @@ -111,6 +112,10 @@ export class SimpleChatModel {
return this.messagesWithContext
}

public setChatTitle(title: string): void {
this.chatTitle = title
}

/**
* Serializes to the legacy transcript JSON format
*/
Expand All @@ -124,6 +129,7 @@ export class SimpleChatModel {
return {
id: this.sessionID,
chatModel: this.modelID,
chatTitle: this.chatTitle,
lastInteractionTimestamp: this.sessionID,
interactions,
}
Expand Down
24 changes: 22 additions & 2 deletions vscode/src/chat/chat-view/SimpleChatPanelProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ export class SimpleChatPanelProvider implements vscode.Disposable, IChatPanelPro
}

const viewType = CodyChatPanelViewType
const panelTitle = getChatPanelTitle(lastQuestion)
const panelTitle = this.history.getChat(this.sessionID)?.chatTitle || getChatPanelTitle(lastQuestion)
const viewColumn = activePanelViewColumn || vscode.ViewColumn.Beside
const webviewPath = vscode.Uri.joinPath(this.extensionUri, 'dist', 'webviews')
const panel = vscode.window.createWebviewPanel(
Expand Down Expand Up @@ -434,6 +434,12 @@ export class SimpleChatPanelProvider implements vscode.Disposable, IChatPanelPro
// and can be removed once we retire the old ChatPanelProvider
return Promise.resolve()
}
public handleChatTitle(title: string): void {
this.chatModel.setChatTitle(title)
if (this.webviewPanel) {
this.webviewPanel.title = title
}
}

public triggerNotice(notice: { key: string }): void {
void this.webview?.postMessage({
Expand Down Expand Up @@ -488,6 +494,10 @@ export class SimpleChatPanelProvider implements vscode.Disposable, IChatPanelPro
// trigger the context progress indicator
this.postViewTranscript({ speaker: 'assistant', text: '' })
await this.generateAssistantResponse(requestID, userContextFiles, addEnhancedContext)
// Set the title of the webview panel to the current text
if (this.webviewPanel) {
this.webviewPanel.title = this.history.getChat(this.sessionID)?.chatTitle || getChatPanelTitle(text)
}
}

private async handleEdit(requestID: string, text: string): Promise<void> {
Expand Down Expand Up @@ -684,6 +694,11 @@ export class SimpleChatPanelProvider implements vscode.Disposable, IChatPanelPro
chatID: this.sessionID,
})

const chatTitle = this.history.getChat(this.sessionID)?.chatTitle
if (chatTitle) {
this.handleChatTitle(chatTitle)
return
}
// Update webview panel title to match the last message
const text = this.chatModel.getLastHumanMessages()?.displayText
if (this.webviewPanel && text) {
Expand Down Expand Up @@ -1244,7 +1259,12 @@ async function newChatModelfromTranscriptJSON(json: TranscriptJSON, defaultModel
]
}
)
return new SimpleChatModel(json.chatModel || defaultModelID, (await Promise.all(messages)).flat(), json.id)
return new SimpleChatModel(
json.chatModel || defaultModelID,
(await Promise.all(messages)).flat(),
json.id,
json.chatTitle
)
}

export function deserializedContextFilesToContextItems(
Expand Down
7 changes: 5 additions & 2 deletions vscode/src/services/treeViewItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,12 @@ export function createCodyChatTreeItems(): CodySidebarTreeItem[] {
const lastDisplayText = lastHumanMessage.humanMessage.displayText.split('\n')[0]
chatTreeItems.push({
id,
title: getChatPanelTitle(lastDisplayText, false),
title: entry.chatTitle || getChatPanelTitle(lastDisplayText, false),
icon: 'comment-discussion',
command: { command: 'cody.chat.panel.restore', args: [id, getChatPanelTitle(lastDisplayText)] },
command: {
command: 'cody.chat.panel.restore',
args: [id, entry.chatTitle || getChatPanelTitle(lastDisplayText)],
},
})
}
})
Expand Down
Loading