From 7563f756652ce33707b7d1614ce761173935b14c Mon Sep 17 00:00:00 2001 From: KENZA BEN JELLOUN Date: Wed, 2 Aug 2023 13:44:42 +0200 Subject: [PATCH 1/9] add delete button --- .../Controllers/DocumentDeleteController.cs | 0 .../CopilotChat/Models/DocumentDeleteForm.cs | 27 +++++++++++++++ webapi/appsettings.json | 2 +- .../src/components/chat/tabs/DocumentsTab.tsx | 33 ++++++++++++++++--- webapp/src/libs/hooks/useFile.ts | 30 ++++++++++++++++- .../libs/services/DocumentDeleteService.ts | 26 +++++++++++++++ 6 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 webapi/CopilotChat/Controllers/DocumentDeleteController.cs create mode 100644 webapi/CopilotChat/Models/DocumentDeleteForm.cs create mode 100644 webapp/src/libs/services/DocumentDeleteService.ts diff --git a/webapi/CopilotChat/Controllers/DocumentDeleteController.cs b/webapi/CopilotChat/Controllers/DocumentDeleteController.cs new file mode 100644 index 000000000..e69de29bb diff --git a/webapi/CopilotChat/Models/DocumentDeleteForm.cs b/webapi/CopilotChat/Models/DocumentDeleteForm.cs new file mode 100644 index 000000000..dff213be8 --- /dev/null +++ b/webapi/CopilotChat/Models/DocumentDeleteForm.cs @@ -0,0 +1,27 @@ +using System; + +namespace SemanticKernel.Service.CopilotChat.Models +{ + /// + /// Form for deleting a document from a POST Http request. + /// + public class DocumentDeleteForm + { + /// + /// The ID of the document to delete. + /// + public Guid DocumentId { get; set; } = Guid.Empty; + + /// + /// The ID of the chat that owns the document. + /// This is used to verify if the user has access to the chat session and delete the document from the appropriate chat. + /// + public Guid ChatId { get; set; } = Guid.Empty; + + /// + /// The ID of the user who is deleting the document from a chat session. + /// This will be used to validate if the user has access to the chat session. + /// + public string UserId { get; set; } = string.Empty; + } +} diff --git a/webapi/appsettings.json b/webapi/appsettings.json index ef20f2a25..8f6aab785 100644 --- a/webapi/appsettings.json +++ b/webapi/appsettings.json @@ -38,7 +38,7 @@ // "AIService": { "Type": "AzureOpenAI", - "Endpoint": "", // ignored when AIService is "OpenAI" + "Endpoint": "https://teams-bot.openai.azure.com/", // ignored when AIService is "OpenAI" // "Key": "", "Models": { "Completion": "gpt-35-turbo", // For OpenAI, change to 'gpt-3.5-turbo' (with a period). diff --git a/webapp/src/components/chat/tabs/DocumentsTab.tsx b/webapp/src/components/chat/tabs/DocumentsTab.tsx index d2e3b0a5e..585f682d3 100644 --- a/webapp/src/components/chat/tabs/DocumentsTab.tsx +++ b/webapp/src/components/chat/tabs/DocumentsTab.tsx @@ -34,7 +34,7 @@ import { import * as React from 'react'; import { useRef } from 'react'; import { useDispatch } from 'react-redux'; -import { useChat } from '../../../libs/hooks'; +import { useChat, useFile } from '../../../libs/hooks'; import { ChatMemorySource } from '../../../libs/models/ChatMemorySource'; import { useAppSelector } from '../../../redux/app/hooks'; import { RootState } from '../../../redux/app/store'; @@ -86,6 +86,7 @@ interface TableItem { export const DocumentsTab: React.FC = () => { const classes = useClasses(); const chat = useChat(); + const fileHandler = useFile(); const dispatch = useDispatch(); const { conversations, selectedId } = useAppSelector((state: RootState) => state.conversations); const { importingDocuments } = conversations[selectedId]; @@ -149,7 +150,17 @@ export const DocumentsTab: React.FC = () => { } }; - const { columns, rows } = useTable(resources); + const handleDelete = async (chatId: string, fileId: string) => { + try { + await fileHandler.deleteFile(chatId, fileId); + // Update the state immediately after deleting the file + setResources((prevResources) => prevResources.filter((resource) => resource.id !== fileId)); + } catch (error) { + console.error('Failed to delete the file:', error); + } + }; + + const { columns, rows } = useTable(resources, handleDelete); return ( { ); }; -function useTable(resources: ChatMemorySource[]) { +function useTable(resources: ChatMemorySource[], handleDelete: (chatId: string, fileId: string) => Promise) { const headerSortProps = (columnId: TableColumnId): TableHeaderCellProps => ({ onClick: (e: React.MouseEvent) => { toggleColumnSort(e, columnId); @@ -308,6 +319,20 @@ function useTable(resources: ChatMemorySource[]) { return getSortDirection('progress') === 'ascending' ? comparison : comparison * -1; }, }), + // Add a new column for the delete button + createTableColumn({ + columnId: 'delete', + renderHeaderCell: () => ( + + Delete + + ), + renderCell: (item) => ( + + + + ), + }), ]; const items = resources.map((item) => ({ @@ -346,7 +371,7 @@ function useTable(resources: ChatMemorySource[]) { }); } - return { columns, rows: items }; + return { columns, rows: items, handleDelete }; } function getAccessString(chatId: string) { diff --git a/webapp/src/libs/hooks/useFile.ts b/webapp/src/libs/hooks/useFile.ts index 3a8caa7c5..196769950 100644 --- a/webapp/src/libs/hooks/useFile.ts +++ b/webapp/src/libs/hooks/useFile.ts @@ -1,6 +1,22 @@ // Copyright (c) Microsoft. All rights reserved. +import { useMsal } from '@azure/msal-react'; +import { useAppSelector } from '../../redux/app/hooks'; +import { RootState } from '../../redux/app/store'; +import { AuthHelper } from '../auth/AuthHelper'; +import { DocumentDeleteService } from '../services/DocumentDeleteService'; + +export interface FileHandler { + loadFile(file: File, loadCallBack: (data: T) => Promise): Promise; + downloadFile(filename: string, content: string, type: string): void; + deleteFile(chatId: string, fileId: string): Promise; + } + +export const useFile = (): FileHandler => { + const { activeUserInfo } = useAppSelector((state: RootState) => state.app); + const userId = activeUserInfo?.id ?? ''; + const documentDeleteService = new DocumentDeleteService(process.env.REACT_APP_BACKEND_URI as string); + const { instance, inProgress } = useMsal(); -export const useFile = () => { async function loadFile(file: File, loadCallBack: (data: T) => Promise): Promise { return await new Promise((resolve, reject) => { const fileReader = new FileReader(); @@ -33,8 +49,20 @@ export const useFile = () => { file = null; } + async function deleteFile(chatId: string, fileId: string): Promise { + try { + + // Call the deleteDocumentAsync method from the DocumentDeleteService + await documentDeleteService.deleteDocumentAsync(userId, chatId, fileId, await AuthHelper.getSKaaSAccessToken(instance, inProgress)); + + } catch (error) { + console.error('Failed to delete the file:', error); + } + } + return { loadFile, downloadFile, + deleteFile, }; }; diff --git a/webapp/src/libs/services/DocumentDeleteService.ts b/webapp/src/libs/services/DocumentDeleteService.ts new file mode 100644 index 000000000..844a61943 --- /dev/null +++ b/webapp/src/libs/services/DocumentDeleteService.ts @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft. All rights reserved. + +import { BaseService } from './BaseService'; + +export class DocumentDeleteService extends BaseService { + public deleteDocumentAsync = async ( + userId: string, + chatId: string, + documentId: string, + accessToken: string, + ) => { + const formData = new FormData(); + formData.append('userId', userId); + formData.append('chatId', chatId); + formData.append('documentId', documentId); + + return await this.getResponseAsync( + { + commandPath: 'deleteDocument', + method: 'POST', + body: formData, + }, + accessToken, + ); + }; +} From f7bfeb9e2704f2e3d97e5113a98f53a0fa200b10 Mon Sep 17 00:00:00 2001 From: Gil LaHaye Date: Wed, 2 Aug 2023 10:41:26 -0700 Subject: [PATCH 2/9] Delete DocumentDeleteController.cs Removing empty file --- webapi/CopilotChat/Controllers/DocumentDeleteController.cs | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 webapi/CopilotChat/Controllers/DocumentDeleteController.cs diff --git a/webapi/CopilotChat/Controllers/DocumentDeleteController.cs b/webapi/CopilotChat/Controllers/DocumentDeleteController.cs deleted file mode 100644 index e69de29bb..000000000 From c7d252c8548dab324f2bfaf8e9edd81c749dce84 Mon Sep 17 00:00:00 2001 From: KENZA BEN JELLOUN Date: Thu, 3 Aug 2023 11:03:22 +0200 Subject: [PATCH 3/9] add DocumentDeleteController --- .../Controllers/DocumentDeleteController.cs | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/webapi/CopilotChat/Controllers/DocumentDeleteController.cs b/webapi/CopilotChat/Controllers/DocumentDeleteController.cs index e69de29bb..48b0ae0b3 100644 --- a/webapi/CopilotChat/Controllers/DocumentDeleteController.cs +++ b/webapi/CopilotChat/Controllers/DocumentDeleteController.cs @@ -0,0 +1,100 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using SemanticKernel.Service.CopilotChat.Models; +using SemanticKernel.Service.CopilotChat.Storage; +using System; +using System.Threading.Tasks; + +namespace SemanticKernel.Service.CopilotChat.Controllers +{ + [Authorize] + [ApiController] + public class DocumentDeleteController : ControllerBase + { + private readonly ILogger _logger; + private readonly ChatSessionRepository _sessionRepository; + private readonly ChatMemorySourceRepository _sourceRepository; + private readonly ChatParticipantRepository _participantRepository; + + + public DocumentDeleteController( + ILogger logger, + ChatSessionRepository sessionRepository, + ChatMemorySourceRepository sourceRepository, + ChatParticipantRepository participantRepository + ) + { + _logger = logger; + _sessionRepository = sessionRepository; + _sourceRepository = sourceRepository; + _participantRepository = participantRepository; + } + + [Route("deleteDocument")] + [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task DeleteDocumentAsync([FromForm] DocumentDeleteForm documentDeleteForm) + { + try + { + await ValidateDocumentDeleteFormAsync(documentDeleteForm); + } + catch (ArgumentException ex) + { + return BadRequest(ex.Message); + } + + // Check if the document exists in the specified chat session. + var memorySource = await _sourceRepository.FindByIdAsync(documentDeleteForm.DocumentId.ToString()); + if (memorySource == null || memorySource.ChatId.ToString() != documentDeleteForm.ChatId.ToString()) + { + return BadRequest("Document not found in the specified chat session."); + } + + // Check if the user has access to the chat session. + if (!await UserHasAccessToChatAsync(documentDeleteForm.UserId, documentDeleteForm.ChatId)) + { + return BadRequest("User does not have access to the chat session."); + } + + try + { + // Delete the document from the repository. + await _sourceRepository.DeleteAsync(memorySource); + } + catch (Exception ex) + { + // Handle any exceptions that occur during the deletion process. + return BadRequest($"Failed to delete the document: {ex.Message}"); + } + + return Ok("Document deleted successfully."); + } + + private async Task ValidateDocumentDeleteFormAsync(DocumentDeleteForm documentDeleteForm) + { + // Make sure the user has access to the chat session where the document exists. + if (!(await UserHasAccessToChatAsync(documentDeleteForm.UserId, documentDeleteForm.ChatId))) + { + throw new ArgumentException("User does not have access to the chat session."); + } + + } + + /// + /// Check if the user has access to the chat session. + /// + /// The user ID. + /// The chat session ID. + /// A boolean indicating whether the user has access to the chat session. + private async Task UserHasAccessToChatAsync(string userId, Guid chatId) + { + return await this._participantRepository.IsUserInChatAsync(userId, chatId.ToString()); + } + + + } +} From 65f7af80060c06034e331f9098dcc01f28c92602 Mon Sep 17 00:00:00 2001 From: kenzabenjelloun <74252706+kenzabenjelloun@users.noreply.github.com> Date: Thu, 3 Aug 2023 11:08:11 +0200 Subject: [PATCH 4/9] Update appsettings.json --- webapi/appsettings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webapi/appsettings.json b/webapi/appsettings.json index 8f6aab785..9aef545c8 100644 --- a/webapi/appsettings.json +++ b/webapi/appsettings.json @@ -38,7 +38,7 @@ // "AIService": { "Type": "AzureOpenAI", - "Endpoint": "https://teams-bot.openai.azure.com/", // ignored when AIService is "OpenAI" + "Endpoint": "", // ignored when AIService is "OpenAI" // "Key": "", "Models": { "Completion": "gpt-35-turbo", // For OpenAI, change to 'gpt-3.5-turbo' (with a period). @@ -232,4 +232,4 @@ // (i.e. dotnet user-secrets set "APPLICATIONINSIGHTS_CONNECTION_STRING" "MY_APPINS_CONNSTRING") // "APPLICATIONINSIGHTS_CONNECTION_STRING": null -} \ No newline at end of file +} From 55686c7c2e957332a6e0d3a26e6ee2c151a18bbf Mon Sep 17 00:00:00 2001 From: Gil LaHaye Date: Thu, 3 Aug 2023 14:09:35 -0700 Subject: [PATCH 5/9] Update webapi/appsettings.json --- webapi/appsettings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/webapi/appsettings.json b/webapi/appsettings.json index 9aef545c8..2a4c6b167 100644 --- a/webapi/appsettings.json +++ b/webapi/appsettings.json @@ -233,3 +233,4 @@ // "APPLICATIONINSIGHTS_CONNECTION_STRING": null } + From 0749ca335d93d10b73b91f6e017e721554cba52d Mon Sep 17 00:00:00 2001 From: KENZA BEN JELLOUN Date: Fri, 1 Sep 2023 12:14:43 +0200 Subject: [PATCH 6/9] putting all the document functionalities together --- .../Controllers/DocumentDeleteController.cs | 100 ------------------ .../Controllers/DocumentImportController.cs | 77 +++++++++++--- webapi/appsettings.json | 5 +- webapp/src/libs/hooks/useFile.ts | 6 +- .../libs/services/DocumentDeleteService.ts | 26 ----- .../libs/services/DocumentImportService.ts | 21 ++++ 6 files changed, 91 insertions(+), 144 deletions(-) delete mode 100644 webapi/CopilotChat/Controllers/DocumentDeleteController.cs delete mode 100644 webapp/src/libs/services/DocumentDeleteService.ts diff --git a/webapi/CopilotChat/Controllers/DocumentDeleteController.cs b/webapi/CopilotChat/Controllers/DocumentDeleteController.cs deleted file mode 100644 index 48b0ae0b3..000000000 --- a/webapi/CopilotChat/Controllers/DocumentDeleteController.cs +++ /dev/null @@ -1,100 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using SemanticKernel.Service.CopilotChat.Models; -using SemanticKernel.Service.CopilotChat.Storage; -using System; -using System.Threading.Tasks; - -namespace SemanticKernel.Service.CopilotChat.Controllers -{ - [Authorize] - [ApiController] - public class DocumentDeleteController : ControllerBase - { - private readonly ILogger _logger; - private readonly ChatSessionRepository _sessionRepository; - private readonly ChatMemorySourceRepository _sourceRepository; - private readonly ChatParticipantRepository _participantRepository; - - - public DocumentDeleteController( - ILogger logger, - ChatSessionRepository sessionRepository, - ChatMemorySourceRepository sourceRepository, - ChatParticipantRepository participantRepository - ) - { - _logger = logger; - _sessionRepository = sessionRepository; - _sourceRepository = sourceRepository; - _participantRepository = participantRepository; - } - - [Route("deleteDocument")] - [HttpPost] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task DeleteDocumentAsync([FromForm] DocumentDeleteForm documentDeleteForm) - { - try - { - await ValidateDocumentDeleteFormAsync(documentDeleteForm); - } - catch (ArgumentException ex) - { - return BadRequest(ex.Message); - } - - // Check if the document exists in the specified chat session. - var memorySource = await _sourceRepository.FindByIdAsync(documentDeleteForm.DocumentId.ToString()); - if (memorySource == null || memorySource.ChatId.ToString() != documentDeleteForm.ChatId.ToString()) - { - return BadRequest("Document not found in the specified chat session."); - } - - // Check if the user has access to the chat session. - if (!await UserHasAccessToChatAsync(documentDeleteForm.UserId, documentDeleteForm.ChatId)) - { - return BadRequest("User does not have access to the chat session."); - } - - try - { - // Delete the document from the repository. - await _sourceRepository.DeleteAsync(memorySource); - } - catch (Exception ex) - { - // Handle any exceptions that occur during the deletion process. - return BadRequest($"Failed to delete the document: {ex.Message}"); - } - - return Ok("Document deleted successfully."); - } - - private async Task ValidateDocumentDeleteFormAsync(DocumentDeleteForm documentDeleteForm) - { - // Make sure the user has access to the chat session where the document exists. - if (!(await UserHasAccessToChatAsync(documentDeleteForm.UserId, documentDeleteForm.ChatId))) - { - throw new ArgumentException("User does not have access to the chat session."); - } - - } - - /// - /// Check if the user has access to the chat session. - /// - /// The user ID. - /// The chat session ID. - /// A boolean indicating whether the user has access to the chat session. - private async Task UserHasAccessToChatAsync(string userId, Guid chatId) - { - return await this._participantRepository.IsUserInChatAsync(userId, chatId.ToString()); - } - - - } -} diff --git a/webapi/CopilotChat/Controllers/DocumentImportController.cs b/webapi/CopilotChat/Controllers/DocumentImportController.cs index 7b665d036..8b24a9e54 100644 --- a/webapi/CopilotChat/Controllers/DocumentImportController.cs +++ b/webapi/CopilotChat/Controllers/DocumentImportController.cs @@ -171,6 +171,50 @@ await messageRelayHubContext.Clients.All.SendAsync( return this.Ok("Documents imported successfully to global scope."); } + [Route("deleteDocument")] + [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task DeleteDocumentAsync([FromForm] DocumentDeleteForm documentDeleteForm) + { + try + { + await ValidateDocumentDeleteFormAsync(documentDeleteForm); + } + catch (ArgumentException ex) + { + return BadRequest(ex.Message); + } + + // Check if the document exists in the specified chat session. + var memorySource = await _sourceRepository.FindByIdAsync(documentDeleteForm.DocumentId.ToString()); + if (memorySource == null || memorySource.ChatId.ToString() != documentDeleteForm.ChatId.ToString()) + { + return BadRequest("Document not found in the specified chat session."); + } + + // Check if the user has access to the chat session. + if (!await UserHasAccessToChatAsync(documentDeleteForm.UserId, documentDeleteForm.ChatId)) + { + return BadRequest("User does not have access to the chat session."); + } + + try + { + // Delete the document from the repository. + await _sourceRepository.DeleteAsync(memorySource); + } + catch (Exception ex) + { + // Handle any exceptions that occur during the deletion process. + return BadRequest($"Failed to delete the document: {ex.Message}"); + } + + return Ok("Document deleted successfully."); + } + + + #region Private /// @@ -271,16 +315,16 @@ private async Task ValidateDocumentImportFormAsync(DocumentImportForm documentIm case SupportedFileType.Jpg: case SupportedFileType.Png: case SupportedFileType.Tiff: - { - if (this._ocrSupportOptions.Type != OcrSupportOptions.OcrSupportType.None) { - break; + if (this._ocrSupportOptions.Type != OcrSupportOptions.OcrSupportType.None) + { + break; + } + + throw new ArgumentException($"Unsupported image file type: {fileType} when " + + $"{OcrSupportOptions.PropertyName}:{nameof(OcrSupportOptions.Type)} is set to " + + nameof(OcrSupportOptions.OcrSupportType.None)); } - - throw new ArgumentException($"Unsupported image file type: {fileType} when " + - $"{OcrSupportOptions.PropertyName}:{nameof(OcrSupportOptions.Type)} is set to " + - nameof(OcrSupportOptions.OcrSupportType.None)); - } default: throw new ArgumentException($"Unsupported file type: {fileType}"); } @@ -310,10 +354,10 @@ private async Task ImportDocumentHelperAsync(IKernel kernel, IForm case SupportedFileType.Jpg: case SupportedFileType.Png: case SupportedFileType.Tiff: - { - documentContent = await this.ReadTextFromImageFileAsync(formFile); - break; - } + { + documentContent = await this.ReadTextFromImageFileAsync(formFile); + break; + } default: // This should never happen. Validation should have already caught this. @@ -593,5 +637,14 @@ private async Task RemoveMemoriesAsync(IKernel kernel, ImportResult importResult } } + private async Task ValidateDocumentDeleteFormAsync(DocumentDeleteForm documentDeleteForm) + { + // Make sure the user has access to the chat session where the document exists. + if (!(await UserHasAccessToChatAsync(documentDeleteForm.UserId, documentDeleteForm.ChatId))) + { + throw new ArgumentException("User does not have access to the chat session."); + } + + } #endregion } diff --git a/webapi/appsettings.json b/webapi/appsettings.json index 2a4c6b167..8f6aab785 100644 --- a/webapi/appsettings.json +++ b/webapi/appsettings.json @@ -38,7 +38,7 @@ // "AIService": { "Type": "AzureOpenAI", - "Endpoint": "", // ignored when AIService is "OpenAI" + "Endpoint": "https://teams-bot.openai.azure.com/", // ignored when AIService is "OpenAI" // "Key": "", "Models": { "Completion": "gpt-35-turbo", // For OpenAI, change to 'gpt-3.5-turbo' (with a period). @@ -232,5 +232,4 @@ // (i.e. dotnet user-secrets set "APPLICATIONINSIGHTS_CONNECTION_STRING" "MY_APPINS_CONNSTRING") // "APPLICATIONINSIGHTS_CONNECTION_STRING": null -} - +} \ No newline at end of file diff --git a/webapp/src/libs/hooks/useFile.ts b/webapp/src/libs/hooks/useFile.ts index 196769950..42c87987e 100644 --- a/webapp/src/libs/hooks/useFile.ts +++ b/webapp/src/libs/hooks/useFile.ts @@ -3,7 +3,7 @@ import { useMsal } from '@azure/msal-react'; import { useAppSelector } from '../../redux/app/hooks'; import { RootState } from '../../redux/app/store'; import { AuthHelper } from '../auth/AuthHelper'; -import { DocumentDeleteService } from '../services/DocumentDeleteService'; +import { DocumentImportService } from '../services/DocumentImportService'; export interface FileHandler { loadFile(file: File, loadCallBack: (data: T) => Promise): Promise; @@ -14,7 +14,7 @@ export interface FileHandler { export const useFile = (): FileHandler => { const { activeUserInfo } = useAppSelector((state: RootState) => state.app); const userId = activeUserInfo?.id ?? ''; - const documentDeleteService = new DocumentDeleteService(process.env.REACT_APP_BACKEND_URI as string); + const documentImportService = new DocumentImportService(process.env.REACT_APP_BACKEND_URI as string); const { instance, inProgress } = useMsal(); async function loadFile(file: File, loadCallBack: (data: T) => Promise): Promise { @@ -53,7 +53,7 @@ export const useFile = (): FileHandler => { try { // Call the deleteDocumentAsync method from the DocumentDeleteService - await documentDeleteService.deleteDocumentAsync(userId, chatId, fileId, await AuthHelper.getSKaaSAccessToken(instance, inProgress)); + await documentImportService.deleteDocumentAsync(userId, chatId, fileId, await AuthHelper.getSKaaSAccessToken(instance, inProgress)); } catch (error) { console.error('Failed to delete the file:', error); diff --git a/webapp/src/libs/services/DocumentDeleteService.ts b/webapp/src/libs/services/DocumentDeleteService.ts deleted file mode 100644 index 844a61943..000000000 --- a/webapp/src/libs/services/DocumentDeleteService.ts +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -import { BaseService } from './BaseService'; - -export class DocumentDeleteService extends BaseService { - public deleteDocumentAsync = async ( - userId: string, - chatId: string, - documentId: string, - accessToken: string, - ) => { - const formData = new FormData(); - formData.append('userId', userId); - formData.append('chatId', chatId); - formData.append('documentId', documentId); - - return await this.getResponseAsync( - { - commandPath: 'deleteDocument', - method: 'POST', - body: formData, - }, - accessToken, - ); - }; -} diff --git a/webapp/src/libs/services/DocumentImportService.ts b/webapp/src/libs/services/DocumentImportService.ts index 7a6622ea2..16c856ad7 100644 --- a/webapp/src/libs/services/DocumentImportService.ts +++ b/webapp/src/libs/services/DocumentImportService.ts @@ -29,4 +29,25 @@ export class DocumentImportService extends BaseService { accessToken, ); }; + + public deleteDocumentAsync = async ( + userId: string, + chatId: string, + documentId: string, + accessToken: string, + ) => { + const formData = new FormData(); + formData.append('userId', userId); + formData.append('chatId', chatId); + formData.append('documentId', documentId); + + return await this.getResponseAsync( + { + commandPath: 'deleteDocument', + method: 'POST', + body: formData, + }, + accessToken, + ); + }; } From 653883fce9387092f98a65f11635621675e643ff Mon Sep 17 00:00:00 2001 From: KENZA BEN JELLOUN Date: Mon, 4 Sep 2023 13:54:52 +0200 Subject: [PATCH 7/9] delete memory collection --- .../CopilotChat/Controllers/DocumentImportController.cs | 8 +++++++- webapi/appsettings.json | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/webapi/CopilotChat/Controllers/DocumentImportController.cs b/webapi/CopilotChat/Controllers/DocumentImportController.cs index 8b24a9e54..239554d07 100644 --- a/webapi/CopilotChat/Controllers/DocumentImportController.cs +++ b/webapi/CopilotChat/Controllers/DocumentImportController.cs @@ -78,6 +78,7 @@ private enum SupportedFileType private const string GlobalDocumentUploadedClientCall = "GlobalDocumentUploaded"; private const string ReceiveMessageClientCall = "ReceiveMessage"; private readonly IOcrEngine _ocrEngine; + private ImportResult _importResult; /// /// Initializes a new instance of the class. @@ -175,7 +176,9 @@ await messageRelayHubContext.Clients.All.SendAsync( [HttpPost] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task DeleteDocumentAsync([FromForm] DocumentDeleteForm documentDeleteForm) + public async Task DeleteDocumentAsync( + [FromServices] IKernel kernel, + [FromForm] DocumentDeleteForm documentDeleteForm) { try { @@ -188,6 +191,7 @@ public async Task DeleteDocumentAsync([FromForm] DocumentDeleteFo // Check if the document exists in the specified chat session. var memorySource = await _sourceRepository.FindByIdAsync(documentDeleteForm.DocumentId.ToString()); + if (memorySource == null || memorySource.ChatId.ToString() != documentDeleteForm.ChatId.ToString()) { return BadRequest("Document not found in the specified chat session."); @@ -203,6 +207,7 @@ public async Task DeleteDocumentAsync([FromForm] DocumentDeleteFo { // Delete the document from the repository. await _sourceRepository.DeleteAsync(memorySource); + await RemoveMemoriesAsync(kernel, _importResult); } catch (Exception ex) { @@ -396,6 +401,7 @@ private async Task ImportDocumentHelperAsync(IKernel kernel, IForm return ImportResult.Fail(); } + _importResult = importResult; return importResult; } diff --git a/webapi/appsettings.json b/webapi/appsettings.json index 8f6aab785..ef20f2a25 100644 --- a/webapi/appsettings.json +++ b/webapi/appsettings.json @@ -38,7 +38,7 @@ // "AIService": { "Type": "AzureOpenAI", - "Endpoint": "https://teams-bot.openai.azure.com/", // ignored when AIService is "OpenAI" + "Endpoint": "", // ignored when AIService is "OpenAI" // "Key": "", "Models": { "Completion": "gpt-35-turbo", // For OpenAI, change to 'gpt-3.5-turbo' (with a period). From cbee911abb7abbacd8c6a936834e7d93e200b246 Mon Sep 17 00:00:00 2001 From: KENZA BEN JELLOUN Date: Mon, 4 Sep 2023 15:35:20 +0200 Subject: [PATCH 8/9] remoe userid from delete form --- webapi/Controllers/DocumentImportController.cs | 4 ++-- webapi/Models/Request/DocumentDeleteForm.cs | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/webapi/Controllers/DocumentImportController.cs b/webapi/Controllers/DocumentImportController.cs index e1ce23504..e150e552f 100644 --- a/webapi/Controllers/DocumentImportController.cs +++ b/webapi/Controllers/DocumentImportController.cs @@ -220,7 +220,7 @@ public async Task DeleteDocumentAsync( } // Check if the user has access to the chat session. - if (!await UserHasAccessToChatAsync(documentDeleteForm.UserId, documentDeleteForm.ChatId)) + if (!await UserHasAccessToChatAsync(this._authInfo.UserId, documentDeleteForm.ChatId)) { return BadRequest("User does not have access to the chat session."); } @@ -695,7 +695,7 @@ private async Task RemoveMemoriesAsync(IKernel kernel, ImportResult importResult private async Task ValidateDocumentDeleteFormAsync(DocumentDeleteForm documentDeleteForm) { // Make sure the user has access to the chat session where the document exists. - if (!(await UserHasAccessToChatAsync(documentDeleteForm.UserId, documentDeleteForm.ChatId))) + if (!(await UserHasAccessToChatAsync(this._authInfo.UserId, documentDeleteForm.ChatId))) { throw new ArgumentException("User does not have access to the chat session."); } diff --git a/webapi/Models/Request/DocumentDeleteForm.cs b/webapi/Models/Request/DocumentDeleteForm.cs index ffd1da582..f27d1d8db 100644 --- a/webapi/Models/Request/DocumentDeleteForm.cs +++ b/webapi/Models/Request/DocumentDeleteForm.cs @@ -24,10 +24,5 @@ public class DocumentDeleteForm /// public Guid ChatId { get; set; } = Guid.Empty; - /// - /// The ID of the user who is deleting the document from a chat session. - /// This will be used to validate if the user has access to the chat session. - /// - public string UserId { get; set; } = string.Empty; } From ff8580e6f7258e246b9131be82e5e968cec5d2be Mon Sep 17 00:00:00 2001 From: KENZA BEN JELLOUN Date: Mon, 4 Sep 2023 15:45:32 +0200 Subject: [PATCH 9/9] remove userid from delete form --- webapp/src/libs/hooks/useFile.ts | 7 ++----- webapp/src/libs/services/DocumentImportService.ts | 2 -- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/webapp/src/libs/hooks/useFile.ts b/webapp/src/libs/hooks/useFile.ts index 4e8ade5ba..8feada6a9 100644 --- a/webapp/src/libs/hooks/useFile.ts +++ b/webapp/src/libs/hooks/useFile.ts @@ -1,8 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. import { useMsal } from '@azure/msal-react'; -import { useAppDispatch, useAppSelector } from '../../redux/app/hooks'; -import { RootState } from '../../redux/app/store'; +import { useAppDispatch } from '../../redux/app/hooks'; import { FeatureKeys } from '../../redux/features/app/AppState'; import { toggleFeatureState } from '../../redux/features/app/appSlice'; import { setImportingDocumentsToConversation } from '../../redux/features/conversations/conversationsSlice'; @@ -11,8 +10,6 @@ import { DocumentImportService } from '../services/DocumentImportService'; import { useChat } from './useChat'; export const useFile = () => { - const { activeUserInfo } = useAppSelector((state: RootState) => state.app); - const userId = activeUserInfo?.id ?? ''; const dispatch = useAppDispatch(); const { instance, inProgress } = useMsal(); @@ -55,7 +52,7 @@ export const useFile = () => { try { // Call the deleteDocumentAsync method from the DocumentDeleteService - await documentImportService.deleteDocumentAsync(userId, chatId, fileId, await AuthHelper.getSKaaSAccessToken(instance, inProgress)); + await documentImportService.deleteDocumentAsync(chatId, fileId, await AuthHelper.getSKaaSAccessToken(instance, inProgress)); } catch (error) { console.error('Failed to delete the file:', error); diff --git a/webapp/src/libs/services/DocumentImportService.ts b/webapp/src/libs/services/DocumentImportService.ts index b38146c2b..c7cbed271 100644 --- a/webapp/src/libs/services/DocumentImportService.ts +++ b/webapp/src/libs/services/DocumentImportService.ts @@ -29,13 +29,11 @@ export class DocumentImportService extends BaseService { }; public deleteDocumentAsync = async ( - userId: string, chatId: string, documentId: string, accessToken: string, ) => { const formData = new FormData(); - formData.append('userId', userId); formData.append('chatId', chatId); formData.append('documentId', documentId);