Merge pull request #182576 from microsoft/roblou/smart-ostrich

Chat import/export fixes
This commit is contained in:
Rob Lourens 2023-05-15 20:16:56 -07:00 committed by GitHub
commit 97f8af3e08
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 73 additions and 54 deletions

View file

@ -29,7 +29,7 @@ import { IChatDetail, IChatService } from 'vs/workbench/contrib/chat/common/chat
import { IChatWidgetHistoryService } from 'vs/workbench/contrib/chat/common/chatWidgetHistoryService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
export const INTERACTIVE_SESSION_CATEGORY = { value: localize('chat.category', "Chat"), original: 'Chat' };
export const CHAT_CATEGORY = { value: localize('chat.category', "Chat"), original: 'Chat' };
export function registerChatActions() {
registerEditorAction(class ChatAcceptInput extends EditorAction {
@ -94,7 +94,7 @@ export function registerChatActions() {
value: localize('interactiveSession.clearHistory.label', "Clear Input History"),
original: 'Clear Input History'
},
category: INTERACTIVE_SESSION_CATEGORY,
category: CHAT_CATEGORY,
f1: true,
});
}
@ -210,7 +210,7 @@ export function registerChatActions() {
value: localize('interactiveSession.clear.label', "Clear"),
original: 'Clear'
},
category: INTERACTIVE_SESSION_CATEGORY,
category: CHAT_CATEGORY,
icon: Codicon.clearAll,
f1: true,
keybinding: {
@ -245,7 +245,7 @@ export function getOpenChatEditorAction(id: string, label: string, when?: string
id: `workbench.action.openChat.${id}`,
title: { value: localize('interactiveSession.open', "Open Editor ({0})", label), original: `Open Editor (${label})` },
f1: true,
category: INTERACTIVE_SESSION_CATEGORY,
category: CHAT_CATEGORY,
precondition: ContextKeyExpr.deserialize(when)
});
}
@ -270,7 +270,7 @@ const getClearChatActionDescriptorForViewTitle = (viewId: string, providerId: st
group: 'navigation',
order: 0
},
category: INTERACTIVE_SESSION_CATEGORY,
category: CHAT_CATEGORY,
icon: Codicon.clearAll,
f1: false
});
@ -300,7 +300,7 @@ const getHistoryChatActionDescriptorForViewTitle = (viewId: string, providerId:
group: 'navigation',
order: 0
},
category: INTERACTIVE_SESSION_CATEGORY,
category: CHAT_CATEGORY,
icon: Codicon.history,
f1: false
});

View file

@ -19,7 +19,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { TerminalLocation } from 'vs/platform/terminal/common/terminal';
import { IUntitledTextResourceEditorInput } from 'vs/workbench/common/editor';
import { INTERACTIVE_SESSION_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { CONTEXT_IN_INTERACTIVE_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { IChatCopyAction, IChatService, IChatUserActionEvent, InteractiveSessionCopyKind } from 'vs/workbench/contrib/chat/common/chatService';
@ -52,7 +52,7 @@ export function registerChatCodeBlockActions() {
original: 'Copy'
},
f1: false,
category: INTERACTIVE_SESSION_CATEGORY,
category: CHAT_CATEGORY,
icon: Codicon.copy,
menu: {
id: MenuId.ChatCodeBlock,
@ -144,7 +144,7 @@ export function registerChatCodeBlockActions() {
original: 'Insert at Cursor'
},
f1: true,
category: INTERACTIVE_SESSION_CATEGORY,
category: CHAT_CATEGORY,
icon: Codicon.insert,
menu: {
id: MenuId.ChatCodeBlock,
@ -252,7 +252,7 @@ export function registerChatCodeBlockActions() {
original: 'Insert Into New File'
},
f1: true,
category: INTERACTIVE_SESSION_CATEGORY,
category: CHAT_CATEGORY,
icon: Codicon.newFile,
menu: {
id: MenuId.ChatCodeBlock,
@ -297,7 +297,7 @@ export function registerChatCodeBlockActions() {
original: 'Run in Terminal'
},
f1: true,
category: INTERACTIVE_SESSION_CATEGORY,
category: CHAT_CATEGORY,
icon: Codicon.terminal,
menu: {
id: MenuId.ChatCodeBlock,
@ -400,7 +400,7 @@ export function registerChatCodeBlockActions() {
when: CONTEXT_IN_INTERACTIVE_SESSION,
},
f1: true,
category: INTERACTIVE_SESSION_CATEGORY,
category: CHAT_CATEGORY,
});
}
@ -423,7 +423,7 @@ export function registerChatCodeBlockActions() {
when: CONTEXT_IN_INTERACTIVE_SESSION,
},
f1: true,
category: INTERACTIVE_SESSION_CATEGORY,
category: CHAT_CATEGORY,
});
}

View file

@ -7,7 +7,7 @@ import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { localize } from 'vs/nls';
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { INTERACTIVE_SESSION_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { IInteractiveRequestViewModel, IInteractiveResponseViewModel, isRequestVM, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
@ -21,7 +21,7 @@ export function registerChatCopyActions() {
original: 'Copy All'
},
f1: false,
category: INTERACTIVE_SESSION_CATEGORY,
category: CHAT_CATEGORY,
menu: {
id: MenuId.ChatContext
}
@ -54,7 +54,7 @@ export function registerChatCopyActions() {
original: 'Copy'
},
f1: false,
category: INTERACTIVE_SESSION_CATEGORY,
category: CHAT_CATEGORY,
menu: {
id: MenuId.ChatContext
}

View file

@ -7,7 +7,7 @@ import { Codicon } from 'vs/base/common/codicons';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { localize } from 'vs/nls';
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
import { INTERACTIVE_SESSION_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { IChatWidget } from 'vs/workbench/contrib/chat/browser/chat';
import { CONTEXT_INTERACTIVE_INPUT_HAS_TEXT, CONTEXT_INTERACTIVE_REQUEST_IN_PROGRESS } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
@ -30,7 +30,7 @@ export function registerChatExecuteActions() {
original: 'Submit'
},
f1: false,
category: INTERACTIVE_SESSION_CATEGORY,
category: CHAT_CATEGORY,
icon: Codicon.send,
precondition: CONTEXT_INTERACTIVE_INPUT_HAS_TEXT,
menu: {
@ -60,7 +60,7 @@ export function registerChatExecuteActions() {
original: 'Cancel'
},
f1: false,
category: INTERACTIVE_SESSION_CATEGORY,
category: CHAT_CATEGORY,
icon: Codicon.debugStop,
menu: {
id: MenuId.ChatExecute,

View file

@ -10,25 +10,25 @@ import { localize } from 'vs/nls';
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IFileService } from 'vs/platform/files/common/files';
import { INTERACTIVE_SESSION_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor';
import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput';
import { isSerializableSessionData } from 'vs/workbench/contrib/chat/common/chatModel';
import { isExportableSessionData } from 'vs/workbench/contrib/chat/common/chatModel';
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
const defaultFileName = 'chat.json';
const filters = [{ name: localize('interactiveSession.file.label', "Chat Session"), extensions: ['json'] }];
const filters = [{ name: localize('chat.file.label', "Chat Session"), extensions: ['json'] }];
export function registerInteractiveSessionExportActions() {
registerAction2(class ExportInteractiveSessionAction extends Action2 {
export function registerChatExportActions() {
registerAction2(class ExportChatAction extends Action2 {
constructor() {
super({
id: 'workbench.action.interactiveSession.export',
category: INTERACTIVE_SESSION_CATEGORY,
id: 'workbench.action.chat.export',
category: CHAT_CATEGORY,
title: {
value: localize('interactiveSession.export.label', "Export Session") + '...',
value: localize('chat.export.label', "Export Session") + '...',
original: 'Export Session...'
},
f1: true,
@ -38,7 +38,7 @@ export function registerInteractiveSessionExportActions() {
const widgetService = accessor.get(IChatWidgetService);
const fileDialogService = accessor.get(IFileDialogService);
const fileService = accessor.get(IFileService);
const interactiveSessionService = accessor.get(IChatService);
const chatService = accessor.get(IChatService);
const widget = widgetService.lastFocusedWidget;
if (!widget || !widget.viewModel) {
@ -54,26 +54,26 @@ export function registerInteractiveSessionExportActions() {
return;
}
const model = interactiveSessionService.getSession(widget.viewModel.sessionId);
const model = chatService.getSession(widget.viewModel.sessionId);
if (!model) {
return;
}
// Using toJSON on the model
const content = VSBuffer.fromString(JSON.stringify(model, undefined, 2));
const content = VSBuffer.fromString(JSON.stringify(model.toExport(), undefined, 2));
await fileService.writeFile(result, content);
}
});
registerAction2(class ImportInteractiveSessionAction extends Action2 {
registerAction2(class ImportChatAction extends Action2 {
constructor() {
super({
id: 'workbench.action.interactiveSession.import',
id: 'workbench.action.chat.import',
title: {
value: localize('interactiveSession.import.label', "Import Session") + '...',
value: localize('chat.import.label', "Import Session") + '...',
original: 'Export Session...'
},
category: INTERACTIVE_SESSION_CATEGORY,
category: CHAT_CATEGORY,
f1: true,
});
}
@ -95,7 +95,7 @@ export function registerInteractiveSessionExportActions() {
const content = await fileService.readFile(result[0]);
try {
const data = JSON.parse(content.value.toString());
if (!isSerializableSessionData(data)) {
if (!isExportableSessionData(data)) {
throw new Error('Invalid chat session data');
}

View file

@ -22,7 +22,7 @@ import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions
import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { IChatReplyFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService';
import { ChatViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { INTERACTIVE_SESSION_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
@ -42,7 +42,7 @@ class AskQuickQuestionAction extends Action2 {
id: 'chat.action.askQuickQuestion',
title: { value: localize('askQuickQuestion', "Ask Quick Question"), original: 'Ask Quick Question' },
f1: true,
category: INTERACTIVE_SESSION_CATEGORY,
category: CHAT_CATEGORY,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyI,

View file

@ -10,7 +10,7 @@ import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
import { localize } from 'vs/nls';
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits';
import { INTERACTIVE_SESSION_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { IChatService, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
@ -29,7 +29,7 @@ export function registerChatTitleActions() {
original: 'Vote Up'
},
f1: false,
category: INTERACTIVE_SESSION_CATEGORY,
category: CHAT_CATEGORY,
icon: Codicon.thumbsup,
toggled: CONTEXT_RESPONSE_VOTE.isEqualTo('up'),
menu: {
@ -68,7 +68,7 @@ export function registerChatTitleActions() {
original: 'Vote Down'
},
f1: false,
category: INTERACTIVE_SESSION_CATEGORY,
category: CHAT_CATEGORY,
icon: Codicon.thumbsdown,
toggled: CONTEXT_RESPONSE_VOTE.isEqualTo('down'),
menu: {
@ -107,7 +107,7 @@ export function registerChatTitleActions() {
original: 'Insert into Notebook'
},
f1: false,
category: INTERACTIVE_SESSION_CATEGORY,
category: CHAT_CATEGORY,
icon: Codicon.insert,
menu: {
id: MenuId.ChatTitle,

View file

@ -21,7 +21,7 @@ import { registerChatCopyActions } from 'vs/workbench/contrib/chat/browser/actio
import { registerChatExecuteActions } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions';
import { registerChatQuickQuestionActions } from 'vs/workbench/contrib/chat/browser/actions/chatQuickInputActions';
import { registerChatTitleActions } from 'vs/workbench/contrib/chat/browser/actions/chatTitleActions';
import { registerInteractiveSessionExportActions } from 'vs/workbench/contrib/chat/browser/actions/interactiveSessionImportExport';
import { registerChatExportActions } from 'vs/workbench/contrib/chat/browser/actions/chatImportExport';
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { ChatContributionService } from 'vs/workbench/contrib/chat/browser/chatContributionServiceImpl';
import { ChatEditor, IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor';
@ -127,7 +127,7 @@ registerChatCodeBlockActions();
registerChatTitleActions();
registerChatExecuteActions();
registerChatQuickQuestionActions();
registerInteractiveSessionExportActions();
registerChatExportActions();
registerSingleton(IChatService, ChatService, InstantiationType.Delayed);
registerSingleton(IChatContributionService, ChatContributionService, InstantiationType.Delayed);

View file

@ -155,6 +155,7 @@ export namespace ChatUri {
interface ISerializedChatEditorInput {
options: IChatEditorOptions;
sessionId: string;
resource: URI;
}
@ -174,6 +175,7 @@ export class ChatEditorInputSerializer implements IEditorSerializer {
const obj: ISerializedChatEditorInput = {
options: input.options,
sessionId: input.sessionId,
resource: input.resource
};
return JSON.stringify(obj);
@ -183,7 +185,7 @@ export class ChatEditorInputSerializer implements IEditorSerializer {
try {
const parsed: ISerializedChatEditorInput = JSON.parse(serializedEditor);
const resource = URI.revive(parsed.resource);
return instantiationService.createInstance(ChatEditorInput, resource, parsed.options as IChatEditorOptions);
return instantiationService.createInstance(ChatEditorInput, resource, { ...parsed.options, target: { sessionId: parsed.sessionId } });
} catch (err) {
return undefined;
}

View file

@ -180,6 +180,8 @@ export interface IChatModel {
readonly inputPlaceholder?: string;
getRequests(): IInteractiveRequestModel[];
waitForInitialization(): Promise<void>;
toExport(): IExportableChatData;
toJSON(): ISerializableChatData;
}
export interface ISerializableChatsData {
@ -196,10 +198,8 @@ export interface ISerializableChatRequestData {
vote: InteractiveSessionVoteDirection | undefined;
}
export interface ISerializableChatData {
sessionId: string;
export interface IExportableChatData {
providerId: string;
creationDate: number;
welcomeMessage: (string | IChatReplyFollowup[])[] | undefined;
requests: ISerializableChatRequestData[];
requesterUsername: string;
@ -209,15 +209,26 @@ export interface ISerializableChatData {
providerState: any;
}
export function isSerializableSessionData(obj: unknown): obj is ISerializableChatData {
const data = obj as ISerializableChatData;
export interface ISerializableChatData extends IExportableChatData {
sessionId: string;
creationDate: number;
}
export function isExportableSessionData(obj: unknown): obj is IExportableChatData {
const data = obj as IExportableChatData;
return typeof data === 'object' &&
typeof data.providerId === 'string' &&
typeof data.sessionId === 'string' &&
typeof data.requesterUsername === 'string' &&
typeof data.responderUsername === 'string';
}
export function isSerializableSessionData(obj: unknown): obj is ISerializableChatData {
const data = obj as ISerializableChatData;
return isExportableSessionData(obj) &&
typeof data.creationDate === 'number' &&
typeof data.sessionId === 'string';
}
export type IChatChangeEvent = IChatAddRequestEvent | IChatAddResponseEvent | IChatInitEvent;
export interface IChatAddRequestEvent {
@ -308,7 +319,7 @@ export class ChatModel extends Disposable implements IChatModel {
@ILogService private readonly logService: ILogService
) {
super();
this._sessionId = initialData ? initialData.sessionId : generateUuid();
this._sessionId = initialData?.sessionId ?? generateUuid();
this._requests = initialData ? this._deserialize(initialData) : [];
this._providerState = initialData ? initialData.providerState : undefined;
this._creationDate = initialData?.creationDate ?? Date.now();
@ -439,10 +450,8 @@ export class ChatModel extends Disposable implements IChatModel {
this._onDidChange.fire({ kind: 'addResponse', response });
}
toJSON(): ISerializableChatData {
toExport(): IExportableChatData {
return {
sessionId: this.sessionId,
creationDate: this._creationDate,
requesterUsername: this._session!.requesterUsername,
requesterAvatarIconUri: this._session!.requesterAvatarIconUri,
responderUsername: this._session!.responderUsername,
@ -470,6 +479,14 @@ export class ChatModel extends Disposable implements IChatModel {
};
}
toJSON(): ISerializableChatData {
return {
...this.toExport(),
sessionId: this.sessionId,
creationDate: this._creationDate,
};
}
override dispose() {
this._session?.dispose?.();
this._requests.forEach(r => r.response?.dispose());