Не подтверждена Коммит 59baae6e создал по автору Justin Chen's avatar Justin Chen Зафиксировано автором GitHub
Просмотр файлов

chat history cleanup (#266587)

* chat history cleanup

* use toolbar instead

* fix flicker
владелец 28580e24
......@@ -230,6 +230,7 @@ export class MenuId {
static readonly ChatCodeBlock = new MenuId('ChatCodeblock');
static readonly ChatCompareBlock = new MenuId('ChatCompareBlock');
static readonly ChatMessageTitle = new MenuId('ChatMessageTitle');
static readonly ChatHistory = new MenuId('ChatHistory');
static readonly ChatMessageFooter = new MenuId('ChatMessageFooter');
static readonly ChatExecute = new MenuId('ChatExecute');
static readonly ChatExecuteSecondary = new MenuId('ChatExecuteSecondary');
......
......@@ -522,6 +522,11 @@ export function registerChatActions() {
id: MenuId.EditorTitle,
when: ActiveEditorContext.isEqualTo(ChatEditorInput.EditorID),
},
{
id: MenuId.ChatHistory,
when: ChatContextKeys.inEmptyStateWithHistoryEnabled,
group: 'navigation',
}
],
category: CHAT_CATEGORY,
icon: Codicon.history,
......
......@@ -81,10 +81,9 @@ import './media/chatViewWelcome.css';
import { ChatViewWelcomePart, IChatSuggestedPrompts, IChatViewWelcomeContent } from './viewsWelcome/chatViewWelcomeController.js';
import { ChatViewPane } from './chatViewPane.js';
import { IViewsService } from '../../../services/views/common/viewsService.js';
import { ICommandService } from '../../../../platform/commands/common/commands.js';
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
import product from '../../../../platform/product/common/product.js';
import { ChatEntitlement, IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js';
import { MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js';
const $ = dom.$;
......@@ -165,7 +164,6 @@ class ChatHistoryListRenderer implements IListRenderer<IChatHistoryListItem, ICh
constructor(
private readonly onDidClickItem: (item: IChatHistoryListItem) => void,
private readonly hoverService: IHoverService,
private readonly formatHistoryTimestamp: (timestamp: number, todayMidnightMs: number) => string,
private readonly todayMidnightMs: number
) { }
......@@ -186,25 +184,12 @@ class ChatHistoryListRenderer implements IListRenderer<IChatHistoryListItem, ICh
renderElement(element: IChatHistoryListItem, index: number, templateData: IChatHistoryTemplate): void {
const { container, title, date, disposables } = templateData;
// Clear previous disposables
disposables.clear();
// Set content
title.textContent = element.title;
date.textContent = this.formatHistoryTimestamp(element.lastMessageDate, this.todayMidnightMs);
// Set accessibility
container.setAttribute('aria-label', element.title);
// Setup hover for full title
const titleHoverEl = dom.$('div.chat-history-item-hover');
titleHoverEl.textContent = element.title;
disposables.add(this.hoverService.setupDelayedHover(container, {
content: titleHoverEl,
appearance: { showPointer: false, compact: true }
}));
// Setup click and keyboard handlers
disposables.add(dom.addDisposableListener(container, dom.EventType.CLICK, () => {
this.onDidClickItem(element);
}));
......@@ -284,6 +269,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
private listContainer!: HTMLElement;
private container!: HTMLElement;
private historyListContainer!: HTMLElement;
get domNode() {
return this.container;
}
......@@ -427,7 +413,6 @@ export class ChatWidget extends Disposable implements IChatWidget {
@ILanguageModelToolsService private readonly toolsService: ILanguageModelToolsService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IChatModeService private readonly chatModeService: IChatModeService,
@IHoverService private readonly hoverService: IHoverService,
@IChatTodoListService private readonly chatTodoListService: IChatTodoListService,
@IChatLayoutService private readonly chatLayoutService: IChatLayoutService,
@IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService,
......@@ -993,6 +978,10 @@ export class ChatWidget extends Disposable implements IChatWidget {
if (this.viewModel) {
dom.setVisibility(numItems === 0, this.welcomeMessageContainer);
dom.setVisibility(numItems !== 0, this.listContainer);
if (numItems > 0) {
this.refreshHistoryList();
}
}
}
......@@ -1010,82 +999,37 @@ export class ChatWidget extends Disposable implements IChatWidget {
const header = dom.append(container, $('.chat-welcome-history-header'));
const headerTitle = dom.append(header, $('.chat-welcome-history-header-title'));
headerTitle.textContent = localize('chat.history.title', 'History');
const headerActions = dom.append(header, $('.chat-welcome-history-header-actions'));
const headerToolbarContainer = dom.append(header, $('.chat-welcome-history-header-toolbar'));
const items = await this.chatService.getHistory();
const filtered = items
.filter(i => !i.isActive)
.sort((a, b) => (b.lastMessageDate ?? 0) - (a.lastMessageDate ?? 0))
.slice(0, 3);
// toolbar for previous sessions
this.historyViewStore.add(this.instantiationService.createInstance(MenuWorkbenchToolBar, headerToolbarContainer, MenuId.ChatHistory, {}));
header.appendChild(headerToolbarContainer);
// If no items to show, hide the entire chat history section
if (filtered.length === 0) {
const initialHistoryItems = await this.computeHistoryItems();
if (initialHistoryItems.length === 0) {
historyRoot.remove();
return;
}
const showAllButton = dom.append(headerActions, $('.chat-welcome-history-show-all'));
showAllButton.classList.add('codicon', `codicon-${Codicon.history.id}`, 'chat-welcome-history-show-all');
showAllButton.tabIndex = 0;
showAllButton.setAttribute('role', 'button');
const showAllHover = localize('chat.history.showAllHover', 'Show history...');
showAllButton.setAttribute('aria-label', showAllHover);
const showAllHoverText = dom.$('div.chat-history-button-hover');
showAllHoverText.textContent = showAllHover;
this.historyViewStore.add(this.hoverService.setupDelayedHover(showAllButton, { content: showAllHoverText, appearance: { showPointer: false, compact: true } }));
this.historyViewStore.add(dom.addDisposableListener(showAllButton, dom.EventType.CLICK, e => {
e.preventDefault();
e.stopPropagation();
setTimeout(() => {
this.instantiationService.invokeFunction(accessor => accessor.get(ICommandService).executeCommand('workbench.action.chat.history'));
}, 0);
}));
this.historyViewStore.add(dom.addStandardDisposableListener(showAllButton, dom.EventType.KEY_DOWN, e => {
if (e.equals(KeyCode.Enter) || e.equals(KeyCode.Space)) {
e.preventDefault();
e.stopPropagation();
setTimeout(() => {
this.instantiationService.invokeFunction(accessor => accessor.get(ICommandService).executeCommand('workbench.action.chat.history'));
}, 0);
}
}));
const welcomeHistoryContainer = dom.append(container, $('.chat-welcome-history-list'));
this.welcomeMessageContainer.classList.toggle('has-chat-history', filtered.length > 0);
this.historyListContainer = dom.append(container, $('.chat-welcome-history-list'));
this.welcomeMessageContainer.classList.toggle('has-chat-history', initialHistoryItems.length > 0);
// Compute today's midnight once for label decisions
const todayMidnight = new Date();
todayMidnight.setHours(0, 0, 0, 0);
const todayMidnightMs = todayMidnight.getTime();
// Create WorkbenchList for chat history items (limit to top 3)
const historyItems: IChatHistoryListItem[] = filtered.slice(0, 3).map(item => ({
sessionId: item.sessionId,
title: item.title,
lastMessageDate: typeof item.lastMessageDate === 'number' ? item.lastMessageDate : Date.now(),
isActive: item.isActive
}));
const listHeight = historyItems.length * 22;
welcomeHistoryContainer.style.height = `${listHeight}px`;
welcomeHistoryContainer.style.minHeight = `${listHeight}px`;
welcomeHistoryContainer.style.overflow = 'hidden';
if (!this.historyList) {
const delegate = new ChatHistoryListDelegate();
const renderer = new ChatHistoryListRenderer(
async (item) => await this.openHistorySession(item.sessionId),
this.hoverService,
(timestamp, todayMs) => this.formatHistoryTimestamp(timestamp, todayMs),
todayMidnightMs
);
const list = this.instantiationService.createInstance(
this.historyList = this._register(this.instantiationService.createInstance(
WorkbenchList<IChatHistoryListItem>,
'ChatHistoryList',
welcomeHistoryContainer,
this.historyListContainer,
delegate,
[renderer],
{
......@@ -1101,24 +1045,53 @@ export class ChatWidget extends Disposable implements IChatWidget {
getWidgetAriaLabel: () => localize('chat.history.list', 'Chat History')
}
}
);
this.historyList = this._register(list);
));
this.historyList.getHTMLElement().tabIndex = -1;
} else {
const currentHistoryList = this.historyList.getHTMLElement();
if (currentHistoryList && currentHistoryList.parentElement !== welcomeHistoryContainer) {
welcomeHistoryContainer.appendChild(currentHistoryList);
if (currentHistoryList && currentHistoryList.parentElement !== this.historyListContainer) {
this.historyListContainer.appendChild(currentHistoryList);
}
}
this.historyList.splice(0, this.historyList.length, historyItems);
this.historyList.layout(undefined, listHeight);
// Deprecated text link replaced by icon button in header
this.renderHistoryItems(initialHistoryItems);
} catch (err) {
this.logService.error('Failed to render welcome history', err);
}
}
private async computeHistoryItems(): Promise<IChatHistoryListItem[]> {
try {
const items = await this.chatService.getHistory();
return items
.filter(i => !i.isActive)
.sort((a, b) => (b.lastMessageDate ?? 0) - (a.lastMessageDate ?? 0))
.slice(0, 3)
.map(item => ({
sessionId: item.sessionId,
title: item.title,
lastMessageDate: typeof item.lastMessageDate === 'number' ? item.lastMessageDate : Date.now(),
isActive: item.isActive
}));
} catch (err) {
this.logService.error('Failed to compute chat history items', err);
return [];
}
}
private renderHistoryItems(historyItems: IChatHistoryListItem[]): void {
if (!this.historyList) {
return;
}
const listHeight = historyItems.length * 22;
if (this.historyListContainer) {
this.historyListContainer.style.height = `${listHeight}px`;
this.historyListContainer.style.minHeight = `${listHeight}px`;
}
this.historyList.splice(0, this.historyList.length, historyItems);
this.historyList.layout(undefined, listHeight);
}
private formatHistoryTimestamp(last: number, todayMidnightMs: number): string {
if (last > todayMidnightMs) {
const diffMs = Date.now() - last;
......@@ -1139,6 +1112,15 @@ export class ChatWidget extends Disposable implements IChatWidget {
}
}
private async refreshHistoryList(): Promise<void> {
const numItems = this.viewModel?.getItems().length ?? 0;
if (numItems === 0 || !this.historyList) {
return;
}
const historyItems = await this.computeHistoryItems();
this.renderHistoryItems(historyItems);
}
private renderChatTodoListWidget(): void {
const sessionId = this.viewModel?.sessionId;
if (!sessionId) {
......
......@@ -247,7 +247,6 @@ div.chat-welcome-view {
}
}
/* Recent history list shown above the welcome content */
.chat-welcome-history-root {
width: 100%;
padding: 0px 8px 0 8px;
......@@ -256,11 +255,16 @@ div.chat-welcome-view {
display: flex;
align-items: center;
justify-content: space-between;
padding: 2px 4px 4px 4px;
padding: 2px 4px 2px 4px;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
min-height: 22px;
color: var(--vscode-descriptionForeground);
.chat-welcome-history-header-toolbar {
padding-right: 15px;
}
}
.chat-welcome-history-header-title {
......@@ -276,21 +280,6 @@ div.chat-welcome-view {
padding-right: 16px;
}
.chat-welcome-history-show-all {
cursor: pointer;
color: var(--vscode-icon-foreground);
padding: 2px;
border-radius: 4px;
}
.chat-welcome-history-show-all:hover {
background: var(--vscode-toolbar-hoverBackground, var(--vscode-list-hoverBackground));
}
.chat-welcome-history-show-all:focus-visible {
outline: 1px solid var(--vscode-focusBorder);
outline-offset: 1px;
}
.chat-welcome-history {
margin: 0 0 12px;
......@@ -313,11 +302,13 @@ div.chat-welcome-view {
align-items: center;
justify-content: space-between;
padding: 2px 12px 4px 12px;
line-height: 18px;
gap: 8px;
cursor: pointer;
outline: none;
&:hover { background: var(--vscode-list-hoverBackground); }
&:hover {
background: var(--vscode-list-hoverBackground);
}
.chat-welcome-history-title {
font-size: 13px;
white-space: nowrap;
......@@ -325,6 +316,7 @@ div.chat-welcome-view {
text-overflow: ellipsis;
flex: 1 1 auto;
}
.chat-welcome-history-date {
font-size: 11px;
color: var(--vscode-descriptionForeground);
......@@ -332,11 +324,4 @@ div.chat-welcome-view {
margin-left: 8px;
}
}
.chat-welcome-history-more {
margin: 4px 0 0;
padding-left: 12px;
a { color: var(--vscode-textLink-foreground); cursor: pointer; }
}
}
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать