Skip to content

Commit

Permalink
Check-point
Browse files Browse the repository at this point in the history
  • Loading branch information
crickman committed Dec 13, 2023
1 parent 0fb2177 commit 7bfe596
Show file tree
Hide file tree
Showing 8 changed files with 268 additions and 137 deletions.
5 changes: 4 additions & 1 deletion webapi/Controllers/DocumentController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public Task<IActionResult> DocumentImportAsync(
/// Service API for removing a document.
/// Documents imported through this route will be considered as global documents.
/// </summary>
[Route("documents")]
[Route("documents/{documentId}")]
[HttpDelete]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
Expand Down Expand Up @@ -238,9 +238,12 @@ private async Task<IActionResult> DocumentRemoveAsync(
return this.BadRequest("Document removal not enabled.");
}

// First remove the document embeddings.
await memoryClient.RemoveDocumentAsync(this._promptOptions.DocumentMemoryName, documentId.ToString(), cancellationToken);

// $$$ REMOVE CITED MESSAGES ???

// Then remove the memory source. This ensures that delete may be re-attempted on exception.
await this._sourceRepository.DeleteAsync(documentId.ToString(), chatId.ToString());

return this.Ok();
Expand Down
1 change: 1 addition & 0 deletions webapi/Extensions/ServiceExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ public static IServiceCollection AddChatCopilotAuthorization(this IServiceCollec
AuthPolicyName.RequireChatParticipant,
builder => builder.RequireAuthenticatedUser().AddRequirements(new ChatParticipantRequirement()));
// $$$ TBD - AUTH
//options.AddPolicy(
// AuthPolicyName.RequireChatAdmin,
// builder => builder.RequireAuthenticatedUser()); //.AddRequirements(new ChatParticipantRequirement())) $$$
Expand Down
278 changes: 143 additions & 135 deletions webapp/src/components/chat/tabs/DocumentsTab.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-misused-promises */ // $$$
// Copyright (c) Microsoft. All rights reserved.

import {
Expand All @@ -8,7 +9,6 @@ import {
MenuList,
MenuPopover,
MenuTrigger,
ProgressBar,
Radio,
RadioGroup,
Spinner,
Expand Down Expand Up @@ -40,6 +40,7 @@ import {
import * as React from 'react';
import { useRef } from 'react';
import { Constants } from '../../../Constants';
import { DeleteDocumentDialog } from './dialogs/DeleteDocumentDialog';
import { useChat, useFile } from '../../../libs/hooks';
import { ChatMemorySource } from '../../../libs/models/ChatMemorySource';
import { useAppSelector } from '../../../redux/app/hooks';
Expand Down Expand Up @@ -106,16 +107,16 @@ export const DocumentsTab: React.FC = () => {
if (!conversations[selectedId].disabled) {
const importingResources = importingDocuments
? importingDocuments.map((document, index) => {
return {
id: `in-progress-${index}`,
chatId: selectedId,
sourceType: 'N/A',
name: document,
sharedBy: 'N/A',
createdOn: 0,
size: 0,
} as ChatMemorySource;
})
return {
id: `in-progress-${index}`,
chatId: selectedId,
sourceType: 'N/A',
name: document,
sharedBy: 'N/A',
createdOn: 0,
size: 0,
} as ChatMemorySource;
})
: [];
setResources(importingResources);

Expand All @@ -127,114 +128,128 @@ export const DocumentsTab: React.FC = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [importingDocuments, selectedId]);

const { columns, rows } = useTable(resources);
const handleDelete = async (chatId: string, documentId: string) => {
try {
await fileHandler.deleteDocument(chatId, documentId);
// Update the state immediately after deleting the file
setResources((prevResources) => prevResources.filter((resource) => resource.id !== documentId));
} catch (error) {
console.error('Failed to delete the file:', error);
}
};

const { columns, rows } = useTable(resources, handleDelete);

return (
<TabView
title="Documents"
learnMoreDescription="document embeddings"
learnMoreLink="https://aka.ms/sk-docs-vectordb"
>
<div className={classes.functional}>
{/* Hidden input for file upload. Only accept .txt and .pdf files for now. */}
<input
type="file"
ref={localDocumentFileRef}
style={{ display: 'none' }}
accept={Constants.app.importTypes}
multiple={true}
onChange={() => {
void fileHandler.handleImport(selectedId, localDocumentFileRef, false);
}}
/>
<input
type="file"
ref={globalDocumentFileRef}
style={{ display: 'none' }}
accept={Constants.app.importTypes}
multiple={true}
onChange={() => {
void fileHandler.handleImport(selectedId, globalDocumentFileRef, true);
}}
/>
<Menu>
<MenuTrigger disableButtonEnhancement>
<Tooltip content="Embed file into chat session" relationship="label">
<Button
className={classes.uploadButton}
icon={<DocumentArrowUp20Regular />}
disabled={
conversations[selectedId].disabled ||
(importingDocuments && importingDocuments.length > 0)
}
>
Upload
</Button>
</Tooltip>
</MenuTrigger>
<MenuPopover>
<MenuList>
<MenuItem
data-testid="addNewLocalDoc"
onClick={() => localDocumentFileRef.current?.click()}
icon={<Add20 />}
disabled={
conversations[selectedId].disabled ||
(importingDocuments && importingDocuments.length > 0)
}
>
New local chat document
</MenuItem>
<MenuItem
data-testid="addNewLocalDoc"
onClick={() => globalDocumentFileRef.current?.click()}
icon={<GlobeAdd20Regular />}
disabled={
conversations[selectedId].disabled ||
(importingDocuments && importingDocuments.length > 0)
}
>
New global document
</MenuItem>
</MenuList>
</MenuPopover>
</Menu>
{importingDocuments && importingDocuments.length > 0 && <Spinner size="tiny" />}
{/* Hardcode vector database as we don't support switching vector store dynamically now. */}
<div className={classes.vectorDatabase}>
<Label size="large">Vector Database:</Label>
<RadioGroup
defaultValue={serviceInfo.memoryStore.selectedType}
layout="horizontal"
disabled={conversations[selectedId].disabled}
>
{serviceInfo.memoryStore.types.map((storeType) => {
return (
<Radio
key={storeType}
value={storeType}
label={storeType}
disabled={storeType !== serviceInfo.memoryStore.selectedType}
/>
);
})}
</RadioGroup>
<>
<TabView
title="Documents"
learnMoreDescription="document embeddings"
learnMoreLink="https://aka.ms/sk-docs-vectordb"
>
<div className={classes.functional}>
{/* Hidden input for file upload. Only accept .txt and .pdf files for now. */}
<input
type="file"
ref={localDocumentFileRef}
style={{ display: 'none' }}
accept={Constants.app.importTypes}
multiple={true}
onChange={() => {
void fileHandler.handleImport(selectedId, localDocumentFileRef, false);
}}
/>
<input
type="file"
ref={globalDocumentFileRef}
style={{ display: 'none' }}
accept={Constants.app.importTypes}
multiple={true}
onChange={() => {
void fileHandler.handleImport(selectedId, globalDocumentFileRef, true);
}}
/>
<Menu>
<MenuTrigger disableButtonEnhancement>
<Tooltip content="Embed file into chat session" relationship="label">
<Button
className={classes.uploadButton}
icon={<DocumentArrowUp20Regular />}
disabled={
conversations[selectedId].disabled ||
(importingDocuments && importingDocuments.length > 0)
}
>
Upload
</Button>
</Tooltip>
</MenuTrigger>
<MenuPopover>
<MenuList>
<MenuItem
data-testid="addNewLocalDoc"
onClick={() => localDocumentFileRef.current?.click()}
icon={<Add20 />}
disabled={
conversations[selectedId].disabled ||
(importingDocuments && importingDocuments.length > 0)
}
>
New local chat document
</MenuItem>
<MenuItem
data-testid="addNewLocalDoc"
onClick={() => globalDocumentFileRef.current?.click()}
icon={<GlobeAdd20Regular />}
disabled={
conversations[selectedId].disabled ||
(importingDocuments && importingDocuments.length > 0)
}
>
New global document
</MenuItem>
</MenuList>
</MenuPopover>
</Menu>
{importingDocuments && importingDocuments.length > 0 && <Spinner size="tiny" />}
{/* Hardcode vector database as we don't support switching vector store dynamically now. */}
<div className={classes.vectorDatabase}>
<Label size="large">Vector Database:</Label>
<RadioGroup
defaultValue={serviceInfo.memoryStore.selectedType}
layout="horizontal"
disabled={conversations[selectedId].disabled}
>
{serviceInfo.memoryStore.types.map((storeType) => {
return (
<Radio
key={storeType}
value={storeType}
label={storeType}
disabled={storeType !== serviceInfo.memoryStore.selectedType}
/>
);
})}
</RadioGroup>
</div>
</div>
</div>
<Table aria-label="External resource table" className={classes.table}>
<TableHeader>
<TableRow>{columns.map((column) => column.renderHeaderCell())}</TableRow>
</TableHeader>
<TableBody>
{rows.map((item) => (
<TableRow key={item.id}>{columns.map((column) => column.renderCell(item))}</TableRow>
))}
</TableBody>
</Table>
</TabView>
);
<Table aria-label="External resource table" className={classes.table}>
<TableHeader>
<TableRow>{columns.map((column) => column.renderHeaderCell())}</TableRow>
</TableHeader>
<TableBody>
{rows.map((item) => (
<TableRow key={item.id}>{columns.map((column) => column.renderCell(item))}</TableRow>
))}
</TableBody>
</Table>
</TabView>
</>);
};

function useTable(resources: ChatMemorySource[]) {
function useTable(resources: ChatMemorySource[], handleDelete: (chatId: string, documentId: string) => Promise<void>) {
const { serviceInfo } = useAppSelector((state: RootState) => state.app);

const headerSortProps = (columnId: TableColumnId): TableHeaderCellProps => ({
onClick: (e: React.MouseEvent) => {
toggleColumnSort(e, columnId);
Expand Down Expand Up @@ -318,29 +333,22 @@ function useTable(resources: ChatMemorySource[]) {
},
}),
createTableColumn<TableItem>({
columnId: 'progress',
columnId: 'delete',
renderHeaderCell: () => (
<TableHeaderCell key="progress" {...headerSortProps('progress')}>
Progress
<TableHeaderCell key="delete">
{(serviceInfo.isDeleteDocumentEnabled ? "Delete" : "")}
</TableHeaderCell>
),
renderCell: (item) => (
<TableCell key={`${item.id}-progress`}>
<ProgressBar
max={1}
value={item.id.startsWith('in-progress') ? undefined : 1} // Hack: tokens stores the progress bar percentage.
shape="rounded"
thickness="large"
color={item.id.startsWith('in-progress') ? 'brand' : 'success'}
/>
renderCell: (item) =>
<TableCell key={`${item.id}-delete`}>
<TableCellLayout truncate>
{(
serviceInfo.isDeleteDocumentEnabled ?
<DeleteDocumentDialog chatId={item.chatId} documentId={item.id} documentName={item.name.label} /> :
<></>
)}
</TableCellLayout>
</TableCell>
),
compare: (a, b) => {
const aAccess = getAccessString(a.chatId);
const bAccess = getAccessString(b.chatId);
const comparison = aAccess.localeCompare(bAccess);
return getSortDirection('progress') === 'ascending' ? comparison : comparison * -1;
},
}),
];

Expand Down Expand Up @@ -380,7 +388,7 @@ function useTable(resources: ChatMemorySource[]) {
});
}

return { columns, rows: items };
return { columns, rows: items, handleDelete };
}

function getAccessString(chatId: string) {
Expand Down
Loading

0 comments on commit 7bfe596

Please sign in to comment.