diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 9c1220d11f5..76997dbc7a6 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -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 }); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 9a1e91e63ea..ce52bf0f58d 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -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, }); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts index ce1ff8e9f47..630ebea22f8 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts @@ -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 } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 0796ae81948..f65fd8725eb 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -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, diff --git a/src/vs/workbench/contrib/chat/browser/actions/interactiveSessionImportExport.ts b/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts similarity index 72% rename from src/vs/workbench/contrib/chat/browser/actions/interactiveSessionImportExport.ts rename to src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts index f8ab9c52299..b9619a2f376 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/interactiveSessionImportExport.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts @@ -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'); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts index 4fb778783a5..7f497bf4bd4 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts @@ -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, diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts index 1557b58281b..40b6a5dc088 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts @@ -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, diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index e5e5ff2bb0a..4dca0d6143e 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -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); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index 2a005782d51..6c1ca45f5c0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -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; } diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 05348362e41..8145d2ded09 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -180,6 +180,8 @@ export interface IChatModel { readonly inputPlaceholder?: string; getRequests(): IInteractiveRequestModel[]; waitForInitialization(): Promise; + 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());