Skip to content

Commit

Permalink
Merge pull request #113 from SciPhi-AI/Nolan/Chunks
Browse files Browse the repository at this point in the history
Chunk management
  • Loading branch information
NolanTrem authored Oct 25, 2024
2 parents 3f4b101 + 5431a9f commit afe1554
Show file tree
Hide file tree
Showing 14 changed files with 1,020 additions and 284 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-menubar": "^1.1.2",
"@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-progress": "^1.1.0",
"@radix-ui/react-select": "^2.1.1",
Expand Down Expand Up @@ -90,7 +91,7 @@
"pnpm": "^9.5.0",
"postcss": "^8.4.39",
"posthog-js": "^1.148.0",
"r2r-js": "^0.3.10",
"r2r-js": "file:/Users/nolantremelling/R2R/js/sdk/r2r-js-0.3.10.tgz",
"radix-ui": "^1.0.1",
"react": "18.3.1",
"react-chartjs-2": "^5.2.0",
Expand Down
354 changes: 172 additions & 182 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

214 changes: 214 additions & 0 deletions src/components/ChatDemo/CreateDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import { Plus, X } from 'lucide-react';
import React, { useState } from 'react';

import { Button } from '@/components/ui/Button';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Input } from '@/components/ui/input'; // Assuming you have this
import { Textarea } from '@/components/ui/textarea'; // Assuming you have this

interface CreateDialogProps {
isOpen: boolean;
onClose: () => void;
onCreateChunks: (
chunks: Array<{ text: string }>,
documentId?: string,
metadata?: Record<string, any>
) => Promise<void>;
}

interface ChunkInput {
id: number;
text: string;
}

export const CreateDialog: React.FC<CreateDialogProps> = ({
isOpen,
onClose,
onCreateChunks,
}) => {
const [chunks, setChunks] = useState<ChunkInput[]>([{ id: 1, text: '' }]);
const [documentId, setDocumentId] = useState<string>('');
const [metadata, setMetadata] = useState<string>('');
const [isMetadataValid, setIsMetadataValid] = useState<boolean>(true);
const [isDocumentIdValid, setIsDocumentIdValid] = useState<boolean>(true);

const validateMetadata = (value: string) => {
if (!value) {
setIsMetadataValid(true);
return;
}

try {
JSON.parse(value);
setIsMetadataValid(true);
} catch {
setIsMetadataValid(false);
}
};

const validateDocumentId = (value: string) => {
if (!value) {
setIsDocumentIdValid(true);
return;
}

const uuidRegex =
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
setIsDocumentIdValid(uuidRegex.test(value));
};

const handleMetadataChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const value = e.target.value;
setMetadata(value);
validateMetadata(value);
};

const handleDocumentIdChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setDocumentId(value);
validateDocumentId(value);
};

const addChunk = () => {
const newId =
chunks.length > 0 ? Math.max(...chunks.map((c) => c.id)) + 1 : 1;
setChunks([...chunks, { id: newId, text: '' }]);
};

const removeChunk = (id: number) => {
if (chunks.length > 1) {
setChunks(chunks.filter((chunk) => chunk.id !== id));
}
};

const updateChunkText = (id: number, text: string) => {
setChunks(
chunks.map((chunk) => (chunk.id === id ? { ...chunk, text } : chunk))
);
};

const handleCreate = async () => {
try {
await onCreateChunks(
chunks.map((chunk) => ({ text: chunk.text })),
documentId || undefined,
metadata ? JSON.parse(metadata) : undefined
);
// Reset form
setChunks([{ id: 1, text: '' }]);
setDocumentId('');
setMetadata('');
onClose();
} catch (error) {
console.error('Error creating chunks:', error);
}
};

return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>Create Chunks</DialogTitle>
</DialogHeader>

<div className="space-y-4">
{/* Optional Document ID */}
<div>
<label className="block text-sm font-medium mb-1">
Document ID
</label>
<Input
value={documentId}
onChange={handleDocumentIdChange}
placeholder="Optional UUID"
className={`${
!isDocumentIdValid
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
: ''
}`}
/>
{!isDocumentIdValid && (
<p className="mt-1 text-sm text-red-500">Invalid UUID format</p>
)}
</div>

{/* Chunks */}
<div className="space-y-4">
<div className="flex justify-between items-center">
<label className="block text-sm font-medium">Chunks</label>
<Button
onClick={addChunk}
color="secondary"
className="flex items-center gap-1"
>
<Plus className="h-4 w-4" />
Add Chunk
</Button>
</div>

{chunks.map((chunk) => (
<div key={chunk.id} className="relative">
<Textarea
value={chunk.text}
onChange={(e) => updateChunkText(chunk.id, e.target.value)}
placeholder="Enter chunk text"
rows={3}
className="pr-8"
/>
{chunks.length > 1 && (
<button
onClick={() => removeChunk(chunk.id)}
className="absolute top-2 right-2 text-red-500 hover:text-red-700"
>
<X size={16} />
</button>
)}
</div>
))}
</div>

{/* Metadata */}
<div>
<label className="block text-sm font-medium mb-1">Metadata</label>
<Textarea
value={metadata}
onChange={handleMetadataChange}
placeholder='Optional JSON: {"key": "value"}'
rows={4}
className={`${
!isMetadataValid
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
: ''
}`}
/>
{!isMetadataValid && (
<p className="mt-1 text-sm text-red-500">Invalid JSON format</p>
)}
</div>
</div>

<div className="flex justify-end gap-2 mt-4">
<Button onClick={onClose} color="secondary">
Cancel
</Button>
<Button
onClick={handleCreate}
disabled={
chunks.some((chunk) => !chunk.text.trim()) ||
!isMetadataValid ||
!isDocumentIdValid
}
color="filled"
>
Create Chunks
</Button>
</div>
</DialogContent>
</Dialog>
);
};
43 changes: 21 additions & 22 deletions src/components/ChatDemo/DocumentsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
PopoverContent,
} from '@/components/ui/popover';
import { useToast } from '@/components/ui/use-toast';
import { IngestionStatus, DocumentInfoType } from '@/types';
import { IngestionStatus, KGExtractionStatus, DocumentInfoType } from '@/types';

interface DocumentsTableProps {
documents: DocumentInfoType[];
Expand Down Expand Up @@ -45,6 +45,8 @@ const DocumentsTable: React.FC<DocumentsTableProps> = ({
onSelectItem,
selectedItems,
hideActions = false,
visibleColumns,
onToggleColumn,
}) => {
const { toast } = useToast();
const [selectedDocumentId, setSelectedDocumentId] = useState('');
Expand All @@ -56,27 +58,13 @@ const DocumentsTable: React.FC<DocumentsTableProps> = ({
}>({ key: 'title', order: 'asc' });
const [filters, setFilters] = useState<Record<string, any>>({
ingestion_status: ['success', 'failed', 'pending'],
kg_extraction_status: ['success', 'failed', 'pending'],
});
const [currentPage, setCurrentPage] = useState(1);
const [searchQuery, setSearchQuery] = useState('');
const [visibleColumns, setVisibleColumns] = useState<Record<string, boolean>>(
{}
);

const itemsPerPage = 10;

useEffect(() => {
const initialVisibility: Record<string, boolean> = {};
columns.forEach((col) => {
initialVisibility[col.key] = col.selected !== false;
});
setVisibleColumns(initialVisibility);
}, []);

const handleToggleColumn = (columnKey: string, isVisible: boolean) => {
setVisibleColumns((prev) => ({ ...prev, [columnKey]: isVisible }));
};

const mapIngestionStatus = (status: string): IngestionStatus => {
const lowerStatus = status?.toLowerCase();
if (lowerStatus === 'success') {
Expand All @@ -88,11 +76,22 @@ const DocumentsTable: React.FC<DocumentsTableProps> = ({
return IngestionStatus.PENDING;
};

const mapKGExtractionStatus = (status: string): KGExtractionStatus => {
const lowerStatus = status?.toLowerCase();
if (lowerStatus === 'success') {
return KGExtractionStatus.SUCCESS;
}
if (lowerStatus === 'failed') {
return KGExtractionStatus.FAILED;
}
return KGExtractionStatus.PENDING;
};

const mappedDocuments = useMemo(() => {
return documents.map((doc) => ({
...doc,
ingestion_status: mapIngestionStatus(doc.ingestion_status),
kg_extraction_status: mapIngestionStatus(doc.kg_extraction_status),
kg_extraction_status: mapKGExtractionStatus(doc.kg_extraction_status),
}));
}, [documents]);

Expand Down Expand Up @@ -148,13 +147,13 @@ const DocumentsTable: React.FC<DocumentsTableProps> = ({
renderCell: (doc) => {
let variant: 'success' | 'destructive' | 'pending' = 'pending';
switch (doc.kg_extraction_status) {
case IngestionStatus.SUCCESS:
case KGExtractionStatus.SUCCESS:
variant = 'success';
break;
case IngestionStatus.FAILED:
case KGExtractionStatus.FAILED:
variant = 'destructive';
break;
case IngestionStatus.PENDING:
case KGExtractionStatus.PENDING:
variant = 'pending';
break;
}
Expand Down Expand Up @@ -263,7 +262,7 @@ const DocumentsTable: React.FC<DocumentsTableProps> = ({
checked: boolean | 'indeterminate'
) => {
if (typeof checked === 'boolean') {
handleToggleColumn(col.key, checked);
onToggleColumn(col.key, checked);
}
}}
/>
Expand Down Expand Up @@ -323,7 +322,7 @@ const DocumentsTable: React.FC<DocumentsTableProps> = ({

<Table
data={filteredDocuments}
columns={columns.filter((col) => visibleColumns[col.key] !== false)}
columns={columns.filter((col) => visibleColumns[col.key] === true)}
onSelectAll={handleSelectAllInternal}
onSelectItem={handleSelectItemInternal}
selectedItems={selectedItems}
Expand Down
27 changes: 13 additions & 14 deletions src/components/ChatDemo/Table.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import {
ChevronUpSquare,
ChevronDownSquare,
Filter,
SlidersHorizontal,
} from 'lucide-react';
import { ChevronUpSquare, ChevronDownSquare, Filter } from 'lucide-react';
import React, { useState, useMemo } from 'react';

import { Button } from '@/components/ui/Button';
import { Checkbox } from '@/components/ui/checkbox';
import CopyableContent from '@/components/ui/CopyableContent';
import Pagination from '@/components/ui/pagination';
Expand Down Expand Up @@ -82,15 +76,21 @@ function Table<T extends { [key: string]: any }>({
const filteredAndSortedData = useMemo(() => {
let result = [...data];

// Apply filters
Object.entries(filters).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
const column = columns.find((col) => col.key === key);
if (
column &&
value !== undefined &&
value !== null &&
value !== '' &&
((Array.isArray(value) && value.length > 0) ||
typeof value === 'string')
) {
result = result.filter((item) => {
const itemValue = (item as any)[key];
const column = columns.find((col) => col.key === key);
if (column?.filterType === 'multiselect') {
return (value as string[]).includes(itemValue);
} else if (column?.filterType === 'select') {
if (column.filterType === 'multiselect') {
return value.includes(itemValue?.toLowerCase());
} else if (column.filterType === 'select') {
return itemValue === value;
} else if (typeof value === 'string') {
return itemValue
Expand All @@ -103,7 +103,6 @@ function Table<T extends { [key: string]: any }>({
}
});

// Apply sorting
if (sort.key) {
result.sort((a, b) => {
const aValue = (a as any)[sort.key];
Expand Down
Loading

0 comments on commit afe1554

Please sign in to comment.