Skip to content

Commit

Permalink
Implement history quick pick (#2250)
Browse files Browse the repository at this point in the history
This PR closes #1692 

- Implemented algorithm to group chats by last interaction timestamp.
- Added an icon in the editor panel to display history chats as a quick
pick, separated by the group.
- Updated the history chats in the Treeview provider to render them as
grouped chats.
- Remove createCodyChatTreeItems, use the updateTree method of
TreeViewProvider to update the list instead.

## Test plan


https://github.com/sourcegraph/cody/assets/44617923/5804d8da-a629-4670-8de9-ff3b3da61e25

---------

Co-authored-by: Tim Lucas <[email protected]>
  • Loading branch information
deepak2431 and toolmantim authored Jan 25, 2024
1 parent 451dd52 commit 35c6cef
Show file tree
Hide file tree
Showing 9 changed files with 292 additions and 54 deletions.
3 changes: 3 additions & 0 deletions vscode/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ This is a log of all notable changes to Cody for VS Code. [Unreleased] changes a

### Added

- Chat: Add a history quick in the editor panel for chats grouped by last interaction timestamp. [pull/2250](https://github.com/sourcegraph/cody/pull/2250)
- Commands: Custom edit commands are now executable from the Chat panel. [pull/2789](https://github.com/sourcegraph/cody/pull/2789)
- [Internal] Edit/Chat: Added "ghost" text alongside code to showcase Edit and Chat commands. [pull/2611](https://github.com/sourcegraph/cody/pull/2611)
- [Internal] Edit/Chat: Added Cmd/Ctrl+K and Cmd/Ctrl+L commands to trigger Edit and Chat [pull/2611](https://github.com/sourcegraph/cody/pull/2611)
Expand Down Expand Up @@ -88,6 +89,8 @@ This is a log of all notable changes to Cody for VS Code. [Unreleased] changes a

### Changed

- Chat: Display chats in the treeview provider grouped by last interaction timestamp. [pull/2250](https://github.com/sourcegraph/cody/pull/2250)
- Autocomplete: Accepting a full line completion will not immedialty start another completion request on the same line. [pulls/2446](https://github.com/sourcegraph/cody/pull/2446)
- Folders named 'bin/' are no longer filtered out from chat `@`-mentions but instead ranked lower. [pull/2472](https://github.com/sourcegraph/cody/pull/2472)
- Files ignored in `.cody/ignore` (if the internal experiment is enabled) will no longer show up in chat `@`-mentions. [pull/2472](https://github.com/sourcegraph/cody/pull/2472)
- Adds a new experiment to test a higher parameter StarCoder model for single-line completions. [pull/2632](https://github.com/sourcegraph/cody/pull/2632)
Expand Down
20 changes: 17 additions & 3 deletions vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,14 @@
"icon": "$(arrow-circle-down)",
"when": "cody.activated && cody.hasChatHistory"
},
{
"command": "cody.chat.history.panel",
"category": "Cody",
"title": "Show History",
"group": "Cody",
"icon": "$(list-unordered)",
"when": "cody.activated && cody.hasChatHistory"
},
{
"command": "cody.search.index-update",
"category": "Cody",
Expand Down Expand Up @@ -707,17 +715,23 @@
"when": "activeWebviewPanelId == cody.chatPanel && cody.activated",
"group": "navigation@1",
"visibility": "visible"
},
{
"command": "cody.chat.history.panel",
"when": "activeWebviewPanelId == cody.chatPanel && cody.activated",
"group": "navigation",
"visibility": "visible"
}
],
"view/item/context": [
{
"command": "cody.chat.history.edit",
"when": "view == cody.chat.tree.view && cody.activated && cody.hasChatHistory",
"when": "view == cody.chat.tree.view && cody.activated && cody.hasChatHistory && viewItem == cody.chats",
"group": "inline@1"
},
{
"command": "cody.chat.history.delete",
"when": "view == cody.chat.tree.view && cody.activated && cody.hasChatHistory",
"when": "view == cody.chat.tree.view && cody.activated && cody.hasChatHistory && viewItem == cody.chats",
"group": "inline@2"
}
]
Expand Down Expand Up @@ -1114,4 +1128,4 @@
"semver": "^7.5.4",
"yaml": "^2.3.4"
}
}
}
5 changes: 1 addition & 4 deletions vscode/src/chat/chat-view/ChatPanelsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import type { SymfRunner } from '../../local-context/symf'
import { logDebug } from '../../log'
import { telemetryService } from '../../services/telemetry'
import { telemetryRecorder } from '../../services/telemetry-v2'
import { createCodyChatTreeItems } from '../../services/treeViewItems'
import { TreeViewProvider } from '../../services/TreeViewProvider'
import type { CachedRemoteEmbeddingsClient } from '../CachedRemoteEmbeddingsClient'
import type { MessageProviderOptions } from '../MessageProvider'
Expand Down Expand Up @@ -231,9 +230,7 @@ export class ChatPanelsManager implements vscode.Disposable {
}

private async updateTreeViewHistory(): Promise<void> {
await this.treeViewProvider.updateTree(
createCodyChatTreeItems(this.options.authProvider.getAuthStatus())
)
await this.treeViewProvider.updateTree(this.options.authProvider.getAuthStatus())
}

public async editChatHistory(chatID: string, label: string): Promise<void> {
Expand Down
3 changes: 1 addition & 2 deletions vscode/src/chat/chat-view/SimpleChatPanelProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ import { getProcessInfo } from '../../services/LocalAppDetector'
import { localStorage } from '../../services/LocalStorageProvider'
import { telemetryService } from '../../services/telemetry'
import { telemetryRecorder } from '../../services/telemetry-v2'
import { createCodyChatTreeItems } from '../../services/treeViewItems'
import type { TreeViewProvider } from '../../services/TreeViewProvider'
import {
handleCodeFromInsertAtCursor,
Expand Down Expand Up @@ -981,7 +980,7 @@ export class SimpleChatPanelProvider implements vscode.Disposable, ChatSession {
messages: allHistory,
})
}
await this.treeView.updateTree(createCodyChatTreeItems(this.authProvider.getAuthStatus()))
await this.treeView.updateTree(this.authProvider.getAuthStatus())
}

public async clearAndRestartSession(): Promise<void> {
Expand Down
4 changes: 4 additions & 0 deletions vscode/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { SearchViewProvider } from './search/SearchViewProvider'
import { AuthProvider } from './services/AuthProvider'
import { showFeedbackSupportQuickPick } from './services/FeedbackOptions'
import { GuardrailsProvider } from './services/GuardrailsProvider'
import { displayHistoryQuickPick } from './services/HistoryChat'
import { localStorage } from './services/LocalStorageProvider'
import { getAccessToken, secretStorage, VSCodeSecretStorage } from './services/SecretStorageProvider'
import { createStatusBar } from './services/StatusBar'
Expand Down Expand Up @@ -380,6 +381,9 @@ const register = async (
query: '@ext:sourcegraph.cody-ai',
})
),
vscode.commands.registerCommand('cody.chat.history.panel', async () => {
await displayHistoryQuickPick(authProvider.getAuthStatus())
}),
vscode.commands.registerCommand('cody.settings.extension.chat', () =>
vscode.commands.executeCommand('workbench.action.openSettings', {
query: '@ext:sourcegraph.cody-ai chat',
Expand Down
151 changes: 151 additions & 0 deletions vscode/src/services/HistoryChat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import * as vscode from 'vscode'

import { getChatPanelTitle } from '../chat/chat-view/chat-helpers'
import { chatHistory } from '../chat/chat-view/ChatHistoryManager'
import type { AuthStatus } from '../chat/protocol'

import type { CodySidebarTreeItem } from './treeViewItems'
import type { InteractionMessage } from '@sourcegraph/cody-shared'

interface GroupedChats {
[groupName: string]: CodySidebarTreeItem[]
}

interface HistoryItem {
label: string
onSelect: () => Promise<void>
kind?: vscode.QuickPickItemKind
}

interface ChatGroup {
[groupName: string]: CodySidebarTreeItem[]
}

const dateEqual = (d1: Date, d2: Date): boolean => {
return d1.getDate() === d2.getDate() && monthYearEqual(d1, d2)
}
const monthYearEqual = (d1: Date, d2: Date): boolean => {
return d1.getMonth() === d2.getMonth() && d1.getFullYear() === d2.getFullYear()
}

export function groupCodyChats(authStatus: AuthStatus | undefined): GroupedChats | null {
const todayChats: CodySidebarTreeItem[] = []
const yesterdayChats: CodySidebarTreeItem[] = []
const thisMonthChats: CodySidebarTreeItem[] = []
const lastMonthChats: CodySidebarTreeItem[] = []
const nMonthsChats: CodySidebarTreeItem[] = []

const today = new Date()
const yesterday = new Date()
yesterday.setDate(yesterday.getDate() - 1)
const lastMonth = new Date()
lastMonth.setDate(0)

const chatGroups: ChatGroup = {
Today: todayChats,
Yesterday: yesterdayChats,
'This month': thisMonthChats,
'Last month': lastMonthChats,
'N months ago': nMonthsChats,
}

if (!authStatus) {
return null
}

const chats = chatHistory.getLocalHistory(authStatus)?.chat
if (!chats) {
return null
}
const chatHistoryEntries = [...Object.entries(chats)]
for (const [id, entry] of chatHistoryEntries) {
let lastHumanMessage: InteractionMessage | undefined = undefined
// Can use Array.prototype.findLast once we drop Node 16
for (let index = entry.interactions.length - 1; index >= 0; index--) {
lastHumanMessage = entry.interactions[index]?.humanMessage
if (lastHumanMessage) {
break
}
}
if (lastHumanMessage?.displayText && lastHumanMessage?.text) {
const lastDisplayText = lastHumanMessage.displayText.split('\n')[0]
const chatTitle = chats[id].chatTitle || getChatPanelTitle(lastDisplayText, false)

const lastInteractionTimestamp = new Date(entry.lastInteractionTimestamp)
let groupLabel = 'N months ago'

if (dateEqual(today, lastInteractionTimestamp)) {
groupLabel = 'Today'
} else if (dateEqual(yesterday, lastInteractionTimestamp)) {
groupLabel = 'Yesterday'
} else if (monthYearEqual(today, lastInteractionTimestamp)) {
groupLabel = 'This month'
} else if (monthYearEqual(lastMonth, lastInteractionTimestamp)) {
groupLabel = 'Last month'
}

const chatGroup = chatGroups[groupLabel]
chatGroup.push({
id,
title: chatTitle,
icon: 'comment-discussion',
command: {
command: 'cody.chat.panel.restore',
args: [id, chatTitle],
},
})
}
}

return {
Today: todayChats.reverse(),
Yesterday: yesterdayChats.reverse(),
'This month': thisMonthChats.reverse(),
'Last month': lastMonthChats.reverse(),
'N months ago': nMonthsChats.reverse(),
}
}

export async function displayHistoryQuickPick(authStatus: AuthStatus): Promise<void> {
const groupedChats = groupCodyChats(authStatus)
if (!groupedChats) {
return
}

const quickPickItems: HistoryItem[] = []

const addGroupSeparator = (groupName: string): void => {
quickPickItems.push({
label: groupName,
onSelect: async () => {},
kind: vscode.QuickPickItemKind.Separator,
})
}

for (const [groupName, chats] of Object.entries(groupedChats)) {
if (chats.length > 0) {
addGroupSeparator(groupName)

for (const chat of chats) {
quickPickItems.push({
label: chat.title,
onSelect: async () => {
await vscode.commands.executeCommand(
'cody.chat.panel.restore',
chat.id,
chat.title
)
},
})
}
}
}

const selectedItem = await vscode.window.showQuickPick(quickPickItems, {
placeHolder: 'Search chat history',
})

if (selectedItem?.onSelect) {
await selectedItem.onSelect()
}
}
10 changes: 5 additions & 5 deletions vscode/src/services/TreeViewProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,28 +67,28 @@ describe('TreeViewProvider', () => {
return nextUpdate
}

function findTreeItem(label: string) {
const items = tree.getChildren()
async function findTreeItem(label: string) {
const items = await tree.getChildren()
return items.find(item => (item.resourceUri as any)?.label === label)
}

describe('Cody Pro Upgrade', () => {
it('is shown when user can upgrade', async () => {
tree = new TreeViewProvider('support', emptyMockFeatureFlagProvider)
await updateTree({ upgradeAvailable: true, endpoint: DOTCOM_URL })
expect(findTreeItem('Upgrade')).not.toBeUndefined()
expect(await findTreeItem('Upgrade')).not.toBeUndefined()
})

it('is not shown when user cannot upgrade', async () => {
tree = new TreeViewProvider('support', emptyMockFeatureFlagProvider)
await updateTree({ upgradeAvailable: false, endpoint: DOTCOM_URL })
expect(findTreeItem('Upgrade')).toBeUndefined()
expect(await findTreeItem('Upgrade')).toBeUndefined()
})

it('is not shown when not dotCom regardless of GA or upgrade flags', async () => {
tree = new TreeViewProvider('support', emptyMockFeatureFlagProvider)
await updateTree({ upgradeAvailable: true, endpoint: new URL('https://example.org') })
expect(findTreeItem('Upgrade')).toBeUndefined()
expect(await findTreeItem('Upgrade')).toBeUndefined()
})
})
})
Loading

0 comments on commit 35c6cef

Please sign in to comment.