fixed spacing and flex

This commit is contained in:
Deon Sanchez 2025-10-06 12:49:39 -06:00
parent 1d4a91eb7b
commit 1dc0a370f1
6 changed files with 641 additions and 782 deletions

View file

@ -25,6 +25,7 @@ import {
FilterIconPopover,
IconKey,
} from "@/components/filter-icon-popover";
import { useLayout } from "@/contexts/layout-context";
interface FacetBucket {
key: string;
@ -48,7 +49,15 @@ export const filterAccentClasses: Record<FilterColor, string> = {
red: "bg-accent-red text-accent-red-foreground",
};
export function KnowledgeFilterPanel() {
interface KnowledgeFilterPanelProps {
totalTopOffset: number;
headerHeight: number;
}
export function KnowledgeFilterPanel({
totalTopOffset,
headerHeight,
}: KnowledgeFilterPanelProps) {
const {
selectedFilter,
parsedFilterData,
@ -135,7 +144,7 @@ export function KnowledgeFilterPanel() {
// Load available facets using search aggregations hook
const { data: aggregations } = useGetSearchAggregations("*", 1, 0, {
enabled: isPanelOpen,
placeholderData: (prev) => prev,
placeholderData: prev => prev,
staleTime: 60_000,
gcTime: 5 * 60_000,
});
@ -213,7 +222,7 @@ export function KnowledgeFilterPanel() {
facetType: keyof typeof selectedFilters,
newValues: string[]
) => {
setSelectedFilters((prev) => ({
setSelectedFilters(prev => ({
...prev,
[facetType]: newValues,
}));
@ -231,7 +240,10 @@ export function KnowledgeFilterPanel() {
};
return (
<div className="fixed right-0 top-14 bottom-0 w-80 bg-background border-l z-40 overflow-y-auto">
<div
className={`fixed right-0 bottom-0 w-80 bg-background border-l z-40 overflow-y-auto`}
style={{ top: `${totalTopOffset}px` }}
>
<Card className="h-full rounded-none border-0 shadow-lg flex flex-col">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
@ -270,7 +282,7 @@ export function KnowledgeFilterPanel() {
<Input
id="filter-name"
value={name}
onChange={(e) => {
onChange={e => {
const v = e.target.value;
setName(v);
if (nameError && v.trim()) {
@ -301,7 +313,7 @@ export function KnowledgeFilterPanel() {
<Textarea
id="filter-description"
value={description}
onChange={(e) => setDescription(e.target.value)}
onChange={e => setDescription(e.target.value)}
placeholder="Provide a brief description of your knowledge filter..."
rows={3}
/>
@ -318,7 +330,7 @@ export function KnowledgeFilterPanel() {
placeholder="Enter your search query..."
value={query}
className="font-mono placeholder:font-mono"
onChange={(e) => setQuery(e.target.value)}
onChange={e => setQuery(e.target.value)}
rows={2}
/>
</div>
@ -327,13 +339,13 @@ export function KnowledgeFilterPanel() {
<div className="space-y-4">
<div className="space-y-2">
<MultiSelect
options={(availableFacets.data_sources || []).map((bucket) => ({
options={(availableFacets.data_sources || []).map(bucket => ({
value: bucket.key,
label: bucket.key,
count: bucket.count,
}))}
value={selectedFilters.data_sources}
onValueChange={(values) =>
onValueChange={values =>
handleFilterChange("data_sources", values)
}
placeholder="Select sources..."
@ -343,15 +355,13 @@ export function KnowledgeFilterPanel() {
<div className="space-y-2">
<MultiSelect
options={(availableFacets.document_types || []).map(
(bucket) => ({
value: bucket.key,
label: bucket.key,
count: bucket.count,
})
)}
options={(availableFacets.document_types || []).map(bucket => ({
value: bucket.key,
label: bucket.key,
count: bucket.count,
}))}
value={selectedFilters.document_types}
onValueChange={(values) =>
onValueChange={values =>
handleFilterChange("document_types", values)
}
placeholder="Select types..."
@ -361,13 +371,13 @@ export function KnowledgeFilterPanel() {
<div className="space-y-2">
<MultiSelect
options={(availableFacets.owners || []).map((bucket) => ({
options={(availableFacets.owners || []).map(bucket => ({
value: bucket.key,
label: bucket.key,
count: bucket.count,
}))}
value={selectedFilters.owners}
onValueChange={(values) => handleFilterChange("owners", values)}
onValueChange={values => handleFilterChange("owners", values)}
placeholder="Select owners..."
allOptionLabel="All Owners"
/>
@ -376,14 +386,14 @@ export function KnowledgeFilterPanel() {
<div className="space-y-2">
<MultiSelect
options={(availableFacets.connector_types || []).map(
(bucket) => ({
bucket => ({
value: bucket.key,
label: bucket.key,
count: bucket.count,
})
)}
value={selectedFilters.connector_types}
onValueChange={(values) =>
onValueChange={values =>
handleFilterChange("connector_types", values)
}
placeholder="Select connectors..."
@ -403,7 +413,7 @@ export function KnowledgeFilterPanel() {
min="1"
max="1000"
value={resultLimit}
onChange={(e) => {
onChange={e => {
const newLimit = Math.max(
1,
Math.min(1000, parseInt(e.target.value) || 1)
@ -416,7 +426,7 @@ export function KnowledgeFilterPanel() {
</div>
<Slider
value={[resultLimit]}
onValueChange={(values) => setResultLimit(values[0])}
onValueChange={values => setResultLimit(values[0])}
max={1000}
min={1}
step={1}
@ -436,7 +446,7 @@ export function KnowledgeFilterPanel() {
max="5"
step="0.1"
value={scoreThreshold}
onChange={(e) =>
onChange={e =>
setScoreThreshold(parseFloat(e.target.value) || 0)
}
className="h-6 text-xs text-right px-2 bg-muted/30 !border-0 rounded ml-auto focus:ring-0 focus:outline-none"
@ -445,7 +455,7 @@ export function KnowledgeFilterPanel() {
</div>
<Slider
value={[scoreThreshold]}
onValueChange={(values) => setScoreThreshold(values[0])}
onValueChange={values => setScoreThreshold(values[0])}
max={5}
min={0}
step={0.1}

View file

@ -2047,21 +2047,10 @@ function ChatPage() {
};
return (
<div
className={`fixed inset-0 md:left-72 flex flex-col transition-all duration-300 ${
isMenuOpen && isPanelOpen
? "md:right-[704px]" // Both open: 384px (menu) + 320px (KF panel)
: isMenuOpen
? "md:right-96" // Only menu open: 384px
: isPanelOpen
? "md:right-80" // Only KF panel open: 320px
: "md:right-6" // Neither open: 24px
}`}
style={{ top: `${totalTopOffset}px` }}
>
<div className="h-full flex flex-col container">
{/* Debug header - only show in debug mode */}
{isDebugMode && (
<div className="flex items-center justify-between mb-6 px-6 pt-6">
<div className="flex items-center justify-between mb-6 px-6 pt-6 ">
<div className="flex items-center gap-2"></div>
<div className="flex items-center gap-4">
{/* Async Mode Toggle */}

View file

@ -133,22 +133,8 @@ function ChunksPageContent() {
}
return (
<div
className={`fixed inset-0 md:left-72 flex flex-row transition-all duration-300 ${
isMenuOpen && isPanelOpen
? "md:right-[704px]"
: // Both open: 384px (menu) + 320px (KF panel)
isMenuOpen
? "md:right-96"
: // Only menu open: 384px
isPanelOpen
? "md:right-80"
: // Only KF panel open: 320px
"md:right-6" // Neither open: 24px
}`}
style={{ top: `${totalTopOffset}px` }}
>
<div className="flex-1 flex flex-col min-h-0 px-6 py-6">
<div className="flex flex-row h-full">
<div className="flex-1 flex flex-col min-h-0">
{/* Header */}
<div className="flex flex-col mb-6">
<div className="flex flex-row items-center gap-3 mb-6">

View file

@ -11,7 +11,6 @@ import { KnowledgeDropdown } from "@/components/knowledge-dropdown";
import { ProtectedRoute } from "@/components/protected-route";
import { Button } from "@/components/ui/button";
import { useKnowledgeFilter } from "@/contexts/knowledge-filter-context";
import { useLayout } from "@/contexts/layout-context";
import { useTask } from "@/contexts/task-context";
import { type File, useGetSearchQuery } from "../api/queries/useGetSearchQuery";
import "@/components/AgGrid/registerAgGridModules";
@ -47,9 +46,8 @@ function getSourceIcon(connectorType?: string) {
function SearchPage() {
const router = useRouter();
const { isMenuOpen, files: taskFiles } = useTask();
const { totalTopOffset } = useLayout();
const { selectedFilter, setSelectedFilter, parsedFilterData, isPanelOpen } =
const { files: taskFiles } = useTask();
const { selectedFilter, setSelectedFilter, parsedFilterData } =
useKnowledgeFilter();
const [selectedRows, setSelectedRows] = useState<File[]>([]);
const [showBulkDeleteDialog, setShowBulkDeleteDialog] = useState(false);
@ -230,116 +228,83 @@ function SearchPage() {
};
return (
<div
className={`fixed inset-0 md:left-72 flex flex-col transition-all duration-300 ${
isMenuOpen && isPanelOpen
? "md:right-[704px]"
: // Both open: 384px (menu) + 320px (KF panel)
isMenuOpen
? "md:right-96"
: // Only menu open: 384px
isPanelOpen
? "md:right-80"
: // Only KF panel open: 320px
"md:right-6" // Neither open: 24px
}`}
style={{ top: `${totalTopOffset}px` }}
>
<div className="flex-1 flex flex-col min-h-0 px-6 py-6">
<div className="flex items-center justify-between mb-6 h-10">
<h2 className="text-lg font-semibold">Project Knowledge</h2>
<KnowledgeDropdown variant="button" />
</div>
<div className="h-full flex flex-col container">
<div className="flex items-center justify-between mb-6">
<h2 className="flex items-center h-[40px] text-lg font-semibold">
Project Knowledge
</h2>
<KnowledgeDropdown variant="button" />
</div>
{/* Search Input Area */}
<div className="flex-shrink-0 mb-6 xl:max-w-[75%]">
<form className="flex gap-3">
<div className="primary-input min-h-10 !flex items-center flex-nowrap focus-within:border-foreground transition-colors !p-[0.3rem]">
{selectedFilter?.name && (
<div
className={`flex items-center gap-1 h-full px-1.5 py-0.5 mr-1 rounded max-w-[25%] ${
filterAccentClasses[parsedFilterData?.color || "zinc"]
}`}
>
<span className="truncate">{selectedFilter?.name}</span>
<X
aria-label="Remove filter"
className="h-4 w-4 flex-shrink-0 cursor-pointer"
onClick={() => setSelectedFilter(null)}
/>
</div>
)}
<Search
className="h-4 w-4 ml-1 flex-shrink-0 text-placeholder-foreground"
strokeWidth={1.5}
/>
<input
className="bg-transparent w-full h-full ml-2 focus:outline-none focus-visible:outline-none font-mono placeholder:font-mono"
name="search-query"
id="search-query"
type="text"
placeholder="Search your documents..."
onChange={handleTableSearch}
/>
</div>
{/* <Button
type="submit"
variant="outline"
className="rounded-lg p-0 flex-shrink-0"
>
{isFetching ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<Search className="h-4 w-4" />
)}
</Button> */}
{/* //TODO: Implement sync button */}
{/* <Button
type="button"
variant="outline"
className="rounded-lg flex-shrink-0"
onClick={() => alert("Not implemented")}
>
Sync
</Button> */}
{selectedRows.length > 0 && (
<Button
type="button"
variant="destructive"
className="rounded-lg flex-shrink-0"
onClick={() => setShowBulkDeleteDialog(true)}
{/* Search Input Area */}
<div className="flex-shrink-0 mb-6 xl:max-w-[75%]">
<form className="flex gap-3">
<div className="primary-input min-h-10 !flex items-center flex-nowrap focus-within:border-foreground transition-colors !p-[0.3rem]">
{selectedFilter?.name && (
<div
className={`flex items-center gap-1 h-full px-1.5 py-0.5 mr-1 rounded max-w-[25%] ${
filterAccentClasses[parsedFilterData?.color || "zinc"]
}`}
>
<Trash2 className="h-4 w-4" /> Delete
</Button>
)}
</form>
</div>
<div className="flex-1 min-h-0 max-h-[700px] overflow-hidden h-full">
<AgGridReact
className="h-full w-full"
columnDefs={columnDefs}
defaultColDef={defaultColDef}
loading={isFetching}
ref={gridRef}
rowData={fileResults}
rowSelection="multiple"
rowMultiSelectWithClick={false}
suppressRowClickSelection={true}
getRowId={params => params.data.filename}
domLayout="normal"
onSelectionChanged={onSelectionChanged}
noRowsOverlayComponent={() => (
<div className="text-center pb-[45px]">
<div className="text-lg text-primary font-semibold">
No knowledge
</div>
<div className="text-sm mt-1 text-muted-foreground">
Add files from local or your preferred cloud.
</div>
<span className="truncate">{selectedFilter?.name}</span>
<X
aria-label="Remove filter"
className="h-4 w-4 flex-shrink-0 cursor-pointer"
onClick={() => setSelectedFilter(null)}
/>
</div>
)}
/>
</div>
<Search
className="h-4 w-4 ml-1 flex-shrink-0 text-placeholder-foreground"
strokeWidth={1.5}
/>
<input
className="bg-transparent w-full h-full ml-2 focus:outline-none focus-visible:outline-none font-mono placeholder:font-mono"
name="search-query"
id="search-query"
type="text"
placeholder="Search your documents..."
onChange={handleTableSearch}
/>
</div>
{selectedRows.length > 0 && (
<Button
type="button"
variant="destructive"
className="rounded-lg flex-shrink-0"
onClick={() => setShowBulkDeleteDialog(true)}
>
<Trash2 className="h-4 w-4" /> Delete
</Button>
)}
</form>
</div>
<div className="flex-1 min-h-0">
<AgGridReact
className="h-full w-full"
columnDefs={columnDefs}
defaultColDef={defaultColDef}
loading={isFetching}
ref={gridRef}
rowData={fileResults}
rowSelection="multiple"
rowMultiSelectWithClick={false}
suppressRowClickSelection={true}
getRowId={params => params.data.filename}
domLayout="normal"
onSelectionChanged={onSelectionChanged}
noRowsOverlayComponent={() => (
<div className="text-center pb-[45px]">
<div className="text-lg text-primary font-semibold">
No knowledge
</div>
<div className="text-sm mt-1 text-muted-foreground">
Add files from local or your preferred cloud.
</div>
</div>
)}
/>
</div>
{/* Bulk Delete Confirmation Dialog */}

File diff suppressed because it is too large Load diff

View file

@ -172,18 +172,16 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) {
headerHeight={headerHeight}
totalTopOffset={totalTopOffset}
>
<div
className={cn(
"py-6 lg:py-8 px-4 lg:px-6",
isSmallWidthPath ? "max-w-[850px]" : "container"
)}
>
<div className={cn("py-6 lg:py-8 px-4 lg:px-6 h-full")}>
{children}
</div>
</LayoutProvider>
</main>
<TaskNotificationMenu />
<KnowledgeFilterPanel />
<KnowledgeFilterPanel
totalTopOffset={totalTopOffset}
headerHeight={headerHeight}
/>
</div>
);
}