diff --git a/Makefile b/Makefile index fe76467a..e8b08a1b 100644 --- a/Makefile +++ b/Makefile @@ -75,6 +75,14 @@ infra: @echo " OpenSearch: http://localhost:9200" @echo " Dashboards: http://localhost:5601" +infra-cpu: + @echo "🔧 Starting infrastructure services only..." + docker-compose -f docker-compose-cpu.yml up -d opensearch dashboards langflow + @echo "✅ Infrastructure services started!" + @echo " Langflow: http://localhost:7860" + @echo " OpenSearch: http://localhost:9200" + @echo " Dashboards: http://localhost:5601" + # Container management stop: @echo "🛑 Stopping all containers..." diff --git a/frontend/components/knowledge-actions-dropdown.tsx b/frontend/components/knowledge-actions-dropdown.tsx new file mode 100644 index 00000000..ecf77e22 --- /dev/null +++ b/frontend/components/knowledge-actions-dropdown.tsx @@ -0,0 +1,25 @@ +"use client"; + +import { EllipsisVertical } from "lucide-react"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { Button } from "./ui/button"; + +export function KnowledgeActionsDropdown() { + return ( + + + + + + Delete + + + ); +} diff --git a/frontend/components/ui/input.tsx b/frontend/components/ui/input.tsx index 5c14e593..3dc0b5f0 100644 --- a/frontend/components/ui/input.tsx +++ b/frontend/components/ui/input.tsx @@ -29,7 +29,7 @@ const Input = React.forwardRef( /> =0.4.0" } }, + "node_modules/ag-charts-types": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/ag-charts-types/-/ag-charts-types-12.2.0.tgz", + "integrity": "sha512-d2qQrQirt9wP36YW5HPuOvXsiajyiFnr1CTsoCbs02bavPDz7Lk2jHp64+waM4YKgXb3GN7gafbBI9Qgk33BmQ==" + }, + "node_modules/ag-grid-community": { + "version": "34.2.0", + "resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-34.2.0.tgz", + "integrity": "sha512-peS7THEMYwpIrwLQHmkRxw/TlOnddD/F5A88RqlBxf8j+WqVYRWMOOhU5TqymGcha7z2oZ8IoL9ROl3gvtdEjg==", + "dependencies": { + "ag-charts-types": "12.2.0" + } + }, + "node_modules/ag-grid-react": { + "version": "34.2.0", + "resolved": "https://registry.npmjs.org/ag-grid-react/-/ag-grid-react-34.2.0.tgz", + "integrity": "sha512-dLKFw6hz75S0HLuZvtcwjm+gyiI4gXVzHEu7lWNafWAX0mb8DhogEOP5wbzAlsN6iCfi7bK/cgZImZFjenlqwg==", + "dependencies": { + "ag-grid-community": "34.2.0", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 185e3866..09dac477 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,6 +26,8 @@ "@tailwindcss/forms": "^0.5.10", "@tailwindcss/typography": "^0.5.16", "@tanstack/react-query": "^5.86.0", + "ag-grid-community": "^34.2.0", + "ag-grid-react": "^34.2.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", diff --git a/frontend/src/app/knowledge/page.tsx b/frontend/src/app/knowledge/page.tsx index 88b93671..8b85313e 100644 --- a/frontend/src/app/knowledge/page.tsx +++ b/frontend/src/app/knowledge/page.tsx @@ -8,7 +8,14 @@ import { Loader2, Search, } from "lucide-react"; -import { type FormEvent, useCallback, useEffect, useState } from "react"; +import { AgGridReact, CustomCellRendererProps } from "ag-grid-react"; +import { + type FormEvent, + useCallback, + useEffect, + useState, + useRef, +} from "react"; import { SiGoogledrive } from "react-icons/si"; import { TbBrandOnedrive } from "react-icons/tb"; import { KnowledgeDropdown } from "@/components/knowledge-dropdown"; @@ -18,6 +25,10 @@ import { Input } from "@/components/ui/input"; import { useKnowledgeFilter } from "@/contexts/knowledge-filter-context"; import { useTask } from "@/contexts/task-context"; import { type File, useGetSearchQuery } from "../api/queries/useGetSearchQuery"; +import { ColDef, RowClickedEvent } from "ag-grid-community"; +import "@/components/AgGrid/registerAgGridModules"; +import "@/components/AgGrid/agGridStyles.css"; +import { KnowledgeActionsDropdown } from "@/components/knowledge-actions-dropdown"; // Function to get the appropriate icon for a connector type function getSourceIcon(connectorType?: string) { @@ -64,11 +75,94 @@ function SearchPage() { } setQuery(queryInputText); }, - [queryInputText, refetchSearch, query], + [queryInputText, refetchSearch, query] ); const fileResults = data as File[]; + const gridRef = useRef(null); + + const [columnDefs] = useState[]>([ + { + field: "filename", + headerName: "Source", + cellRenderer: ({ data, value }: CustomCellRendererProps) => { + return ( +
+ {getSourceIcon(data?.connector_type)} + + {value} + +
+ ); + }, + }, + { + field: "size", + headerName: "Size", + valueFormatter: (params) => + params.value ? `${Math.round(params.value / 1024)} KB` : "-", + }, + { + field: "mimetype", + headerName: "Type", + }, + { + field: "owner", + headerName: "Owner", + valueFormatter: (params) => + params.value || + params.data?.owner_name || + params.data?.owner_email || + "—", + }, + + { + field: "chunkCount", + headerName: "Chunks", + }, + { + field: "avgScore", + headerName: "Avg score", + cellRenderer: ({ value }: CustomCellRendererProps) => { + return ( + + {value.toFixed(2)} + + ); + }, + }, + { + cellRenderer: () => { + return ; + }, + cellStyle: { + alignItems: 'center', + display: 'flex', + justifyContent: 'center', + padding: 0, + }, + colId: 'actions', + filter: false, + maxWidth: 60, + minWidth: 60, + resizable: false, + sortable: false, + initialFlex: 0, + }, + ]); + + const defaultColDef: ColDef = { + cellStyle: () => ({ + display: "flex", + alignItems: "center", + }), + initialFlex: 1, + minWidth: 100, + resizable: false, + suppressMovable: true, + }; + return (
+
+

Project Knowledge

+ +
{/* Search Input Area */} -
+
-
- -
- - {/* Results Area */} -
-
- {fileResults.length === 0 && !isFetching ? ( -
+ {selectedFile ? ( + // Show chunks for selected file + <> +
+ + + Chunks from {selectedFile} + +
+ {fileResults + .filter((file) => file.filename === selectedFile) + .flatMap((file) => file.chunks) + .map((chunk, index) => ( +
+
+
+ + + {chunk.filename} + +
+ + {chunk.score.toFixed(2)} + +
+
+ {chunk.mimetype} • Page {chunk.page} +
+

+ {chunk.text} +

+
+ ))} + + ) : ( + ) => { + setSelectedFile(params.data?.filename ?? ""); + }} + noRowsOverlayComponent={() => ( +

No documents found @@ -128,140 +272,9 @@ function SearchPage() { Try adjusting your search terms

- ) : ( -
- {/* Results Count */} -
-
- {fileResults.length} file - {fileResults.length !== 1 ? "s" : ""} found -
-
- - {/* Results Display */} -
- {selectedFile ? ( - // Show chunks for selected file - <> -
- - - Chunks from {selectedFile} - -
- {fileResults - .filter((file) => file.filename === selectedFile) - .flatMap((file) => file.chunks) - .map((chunk, index) => ( -
-
-
- - - {chunk.filename} - -
- - {chunk.score.toFixed(2)} - -
-
- {chunk.mimetype} • Page {chunk.page} -
-

- {chunk.text} -

-
- ))} - - ) : ( - // Show files table -
- - - - - - - - - - - - - {fileResults.map((file) => ( - setSelectedFile(file.filename)} - > - - - - - - - - ))} - -
- Source - - Type - - Size - - Matching chunks - - Average score - - Owner -
-
- {getSourceIcon(file.connector_type)} - - {file.filename} - -
-
- {file.mimetype} - - {file.size - ? `${Math.round(file.size / 1024)} KB` - : "—"} - - {file.chunkCount} - - - {file.avgScore.toFixed(2)} - - - {file.owner_name || file.owner || "—"} -
-
- )} -
-
)} -
-
+ /> + )}
); diff --git a/frontend/src/components/AgGrid/agGridStyles.css b/frontend/src/components/AgGrid/agGridStyles.css new file mode 100644 index 00000000..b595e18c --- /dev/null +++ b/frontend/src/components/AgGrid/agGridStyles.css @@ -0,0 +1,21 @@ +body { + --ag-text-color: hsl(var(--muted-foreground)); + --ag-background-color: hsl(var(--background)); + --ag-header-background-color: hsl(var(--background)); + --ag-header-text-color: hsl(var(--muted-foreground)); + --ag-header-column-resize-handle-color: hsl(var(--border)); + --ag-header-row-border: hsl(var(--border)); + --ag-header-font-weight: var(--font-medium); + --ag-row-border: undefined; + --ag-row-hover-color: hsl(var(--muted)); + --ag-wrapper-border: none; + --ag-font-family: var(--font-sans); + + .ag-header { + border-bottom: 1px solid hsl(var(--border)); + margin-bottom: 0.5rem; + } + .ag-row { + cursor: pointer; + } +} diff --git a/frontend/src/components/AgGrid/registerAgGridModules.ts b/frontend/src/components/AgGrid/registerAgGridModules.ts new file mode 100644 index 00000000..da2c5280 --- /dev/null +++ b/frontend/src/components/AgGrid/registerAgGridModules.ts @@ -0,0 +1,33 @@ +import { + ModuleRegistry, + ValidationModule, + ColumnAutoSizeModule, + ColumnApiModule, + PaginationModule, + CellStyleModule, + QuickFilterModule, + ClientSideRowModelModule, + TextFilterModule, + DateFilterModule, + EventApiModule, + GridStateModule, + } from 'ag-grid-community'; + + // Importing necessary modules from ag-grid-community + // https://www.ag-grid.com/javascript-data-grid/modules/#selecting-modules + + ModuleRegistry.registerModules([ + ColumnAutoSizeModule, + ColumnApiModule, + PaginationModule, + CellStyleModule, + QuickFilterModule, + ClientSideRowModelModule, + TextFilterModule, + DateFilterModule, + EventApiModule, + GridStateModule, + // The ValidationModule adds helpful console warnings/errors that can help identify bad configuration during development. + ...(process.env.NODE_ENV !== 'production' ? [ValidationModule] : []), + ]); + \ No newline at end of file