Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
Deon Sanchez
43b88a3f7e Enhance Ingest Settings UI and functionality
- Added a new 'tableStructure' setting to the IngestSettings interface and UI.
- Updated the IngestSettings component to include a select input for the embedding model.
- Improved layout and accessibility of input fields and switches.
- Integrated default settings for chunk size, chunk overlap, OCR, picture descriptions, and embedding model from constants.
- Implemented synchronization of settings with backend data using React Query.
2025-09-30 10:24:19 -06:00
4 changed files with 211 additions and 75 deletions

View file

@ -2,13 +2,22 @@
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
import { import {
Collapsible, Collapsible,
CollapsibleContent, CollapsibleContent,
CollapsibleTrigger, CollapsibleTrigger,
} from "@/components/ui/collapsible"; } from "@/components/ui/collapsible";
import { ChevronRight, Info } from "lucide-react"; import { ChevronRight } from "lucide-react";
import { IngestSettings as IngestSettingsType } from "./types"; import { IngestSettings as IngestSettingsType } from "./types";
import { LabelWrapper } from "@/components/label-wrapper";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
interface IngestSettingsProps { interface IngestSettingsProps {
isOpen: boolean; isOpen: boolean;
@ -30,6 +39,7 @@ export const IngestSettings = ({
ocr: false, ocr: false,
pictureDescriptions: false, pictureDescriptions: false,
embeddingModel: "text-embedding-3-small", embeddingModel: "text-embedding-3-small",
tableStructure: false,
}; };
// Use provided settings or defaults // Use provided settings or defaults
@ -58,79 +68,144 @@ export const IngestSettings = ({
</CollapsibleTrigger> </CollapsibleTrigger>
<CollapsibleContent className="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:slide-up-2 data-[state=open]:slide-down-2"> <CollapsibleContent className="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:slide-up-2 data-[state=open]:slide-down-2">
<div className="pt-5 space-y-5"> <div className="pt-5 space-y-6">
<div className="flex items-center gap-4 w-full"> <div className="space-y-2">
<div className="w-full"> <LabelWrapper
<div className="text-sm mb-2 font-semibold">Chunk size</div> helperText="Model used for knowledge ingest and retrieval"
<Input id="embedding-model-select"
type="number" label="Embedding model"
value={currentSettings.chunkSize} >
onChange={e => <Select
handleSettingsChange({ disabled={true}
chunkSize: parseInt(e.target.value) || 0, value={currentSettings.embeddingModel}
}) onValueChange={value =>
handleSettingsChange({ embeddingModel: value })
}
>
<SelectTrigger disabled id="embedding-model-select">
<SelectValue placeholder="text-embedding-3-small" />
</SelectTrigger>
<SelectContent>
<SelectItem value={currentSettings.embeddingModel}>
{currentSettings.embeddingModel}
</SelectItem>
</SelectContent>
</Select>
</LabelWrapper>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="chunk-size" className="text-sm font-medium">
Chunk size
</Label>
<div className="relative">
<Input
id="chunk-size"
type="number"
min="1"
value={currentSettings.chunkSize}
onChange={e =>
handleSettingsChange({
chunkSize: Math.max(0, parseInt(e.target.value) || 0),
})
}
className="w-full pr-24"
/>
<div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
<span className="text-sm text-muted-foreground">
characters
</span>
</div>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="chunk-overlap" className="text-sm font-medium">
Chunk overlap
</Label>
<div className="relative">
<Input
id="chunk-overlap"
type="number"
min="0"
value={currentSettings.chunkOverlap}
onChange={e =>
handleSettingsChange({
chunkOverlap: Math.max(0, parseInt(e.target.value) || 0),
})
}
className="w-full pr-24"
/>
<div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
<span className="text-sm text-muted-foreground">
characters
</span>
</div>
</div>
</div>
</div>
<div className="space-y-6">
<div className="flex items-center space-x-3">
<div className="flex-1">
<Label
htmlFor="ocr-toggle"
className="text-base font-medium cursor-pointer text-sm"
>
Table Structure
</Label>
<div className="text-sm text-muted-foreground">
Capture table structure during ingest.
</div>
</div>
<Switch
id="ocr-toggle"
checked={currentSettings.tableStructure}
onCheckedChange={checked =>
handleSettingsChange({ tableStructure: checked })
} }
/> />
</div> </div>
<div className="w-full"> <div className="flex items-center space-x-3">
<div className="text-sm mb-2 font-semibold">Chunk overlap</div> <div className="flex-1">
<Input <Label
type="number" htmlFor="ocr-toggle"
value={currentSettings.chunkOverlap} className="text-base font-medium cursor-pointer text-sm"
onChange={e => >
handleSettingsChange({ OCR
chunkOverlap: parseInt(e.target.value) || 0, </Label>
}) <div className="text-sm text-muted-foreground">
Extracts text from images and scanned pages.
</div>
</div>
<Switch
id="ocr-toggle"
checked={currentSettings.ocr}
onCheckedChange={checked =>
handleSettingsChange({ ocr: checked })
} }
/> />
</div> </div>
</div> <div className="flex items-center space-x-3">
<div className="flex-1">
<div className="flex gap-2 items-center justify-between"> <Label
<div> htmlFor="picture-descriptions-toggle"
<div className="text-sm font-semibold pb-2">OCR</div> className="text-base font-medium cursor-pointer text-sm"
<div className="text-sm text-muted-foreground"> >
Extracts text from images/PDFs. Ingest is slower when enabled. Picture descriptions
</Label>
<div className="text-sm text-muted-foreground">
Generates short image captions. More expensive when enabled.
</div>
</div> </div>
<Switch
id="picture-descriptions-toggle"
checked={currentSettings.pictureDescriptions}
onCheckedChange={checked =>
handleSettingsChange({ pictureDescriptions: checked })
}
/>
</div> </div>
<Switch
checked={currentSettings.ocr}
onCheckedChange={checked =>
handleSettingsChange({ ocr: checked })
}
/>
</div>
<div className="flex gap-2 items-center justify-between">
<div>
<div className="text-sm pb-2 font-semibold">
Picture descriptions
</div>
<div className="text-sm text-muted-foreground">
Adds captions for images. Ingest is more expensive when enabled.
</div>
</div>
<Switch
checked={currentSettings.pictureDescriptions}
onCheckedChange={checked =>
handleSettingsChange({ pictureDescriptions: checked })
}
/>
</div>
<div>
<div className="text-sm font-semibold pb-2 flex items-center">
Embedding model
<Info className="w-3.5 h-3.5 text-muted-foreground ml-2" />
</div>
<Input
disabled
value={currentSettings.embeddingModel}
onChange={e =>
handleSettingsChange({ embeddingModel: e.target.value })
}
placeholder="text-embedding-3-small"
/>
</div> </div>
</div> </div>
</CollapsibleContent> </CollapsibleContent>

View file

@ -103,4 +103,5 @@ export interface IngestSettings {
ocr: boolean; ocr: boolean;
pictureDescriptions: boolean; pictureDescriptions: boolean;
embeddingModel: string; embeddingModel: string;
tableStructure: boolean;
} }

View file

@ -10,6 +10,9 @@ import { PickerHeader } from "./picker-header";
import { FileList } from "./file-list"; import { FileList } from "./file-list";
import { IngestSettings } from "./ingest-settings"; import { IngestSettings } from "./ingest-settings";
import { createProviderHandler } from "./provider-handlers"; import { createProviderHandler } from "./provider-handlers";
import { useGetSettingsQuery } from "@/app/api/queries/useGetSettingsQuery";
import { useAuth } from "@/contexts/auth-context";
import { DEFAULT_KNOWLEDGE_SETTINGS } from "@/lib/constants";
export const UnifiedCloudPicker = ({ export const UnifiedCloudPicker = ({
provider, provider,
@ -22,21 +25,73 @@ export const UnifiedCloudPicker = ({
baseUrl, baseUrl,
onSettingsChange, onSettingsChange,
}: UnifiedCloudPickerProps) => { }: UnifiedCloudPickerProps) => {
const { isNoAuthMode } = useAuth();
const [isPickerLoaded, setIsPickerLoaded] = useState(false); const [isPickerLoaded, setIsPickerLoaded] = useState(false);
const [isPickerOpen, setIsPickerOpen] = useState(false); const [isPickerOpen, setIsPickerOpen] = useState(false);
const [isIngestSettingsOpen, setIsIngestSettingsOpen] = useState(false); const [isIngestSettingsOpen, setIsIngestSettingsOpen] = useState(true);
const [isLoadingBaseUrl, setIsLoadingBaseUrl] = useState(false); const [isLoadingBaseUrl, setIsLoadingBaseUrl] = useState(false);
const [autoBaseUrl, setAutoBaseUrl] = useState<string | undefined>(undefined); const [autoBaseUrl, setAutoBaseUrl] = useState<string | undefined>(undefined);
// Fetch settings using React Query
const { data: settings = {} } = useGetSettingsQuery({
enabled: isAuthenticated || isNoAuthMode,
});
// Settings state with defaults // Settings state with defaults
const [ingestSettings, setIngestSettings] = useState<IngestSettingsType>({ const [ingestSettings, setIngestSettings] = useState<IngestSettingsType>({
chunkSize: 1000, chunkSize: DEFAULT_KNOWLEDGE_SETTINGS.chunk_size,
chunkOverlap: 200, chunkOverlap: DEFAULT_KNOWLEDGE_SETTINGS.chunk_overlap,
ocr: false, ocr: DEFAULT_KNOWLEDGE_SETTINGS.ocr,
pictureDescriptions: false, pictureDescriptions: DEFAULT_KNOWLEDGE_SETTINGS.picture_descriptions,
embeddingModel: "text-embedding-3-small", embeddingModel: DEFAULT_KNOWLEDGE_SETTINGS.embedding_model,
tableStructure: DEFAULT_KNOWLEDGE_SETTINGS.table_structure,
}); });
// Sync chunk size with backend settings
useEffect(() => {
const chunkSize = settings.knowledge?.chunk_size;
if (chunkSize !== undefined) {
setIngestSettings(prev => ({
...prev,
chunkSize: chunkSize,
}));
}
}, [settings.knowledge]);
// Sync chunk overlap with backend settings
useEffect(() => {
const chunkOverlap = settings.knowledge?.chunk_overlap;
if (chunkOverlap !== undefined) {
setIngestSettings(prev => ({
...prev,
chunkOverlap: chunkOverlap,
}));
}
}, [settings.knowledge]);
// Sync processing mode (doclingPresets) with OCR and picture descriptions
useEffect(() => {
const mode = settings.knowledge?.doclingPresets;
if (mode) {
setIngestSettings(prev => ({
...prev,
ocr: mode === "ocr" || mode === "picture_description" || mode === "VLM",
pictureDescriptions: mode === "picture_description",
}));
}
}, [settings.knowledge]);
// Sync embedding model with backend settings
useEffect(() => {
const embeddingModel = settings.knowledge?.embedding_model;
if (embeddingModel) {
setIngestSettings(prev => ({
...prev,
embeddingModel: embeddingModel,
}));
}
}, [settings.knowledge]);
// Handle settings changes and notify parent // Handle settings changes and notify parent
const handleSettingsChange = (newSettings: IngestSettingsType) => { const handleSettingsChange = (newSettings: IngestSettingsType) => {
setIngestSettings(newSettings); setIngestSettings(newSettings);

View file

@ -3,7 +3,8 @@
*/ */
export const DEFAULT_AGENT_SETTINGS = { export const DEFAULT_AGENT_SETTINGS = {
llm_model: "gpt-4o-mini", llm_model: "gpt-4o-mini",
system_prompt: "You are a helpful assistant that can use tools to answer questions and perform tasks." system_prompt:
"You are a helpful assistant that can use tools to answer questions and perform tasks.",
} as const; } as const;
/** /**
@ -12,7 +13,11 @@ export const DEFAULT_AGENT_SETTINGS = {
export const DEFAULT_KNOWLEDGE_SETTINGS = { export const DEFAULT_KNOWLEDGE_SETTINGS = {
chunk_size: 1000, chunk_size: 1000,
chunk_overlap: 200, chunk_overlap: 200,
processing_mode: "standard" processing_mode: "standard",
table_structure: false,
ocr: false,
picture_descriptions: false,
embedding_model: "text-embedding-3-small",
} as const; } as const;
/** /**
@ -20,4 +25,4 @@ export const DEFAULT_KNOWLEDGE_SETTINGS = {
*/ */
export const UI_CONSTANTS = { export const UI_CONSTANTS = {
MAX_SYSTEM_PROMPT_CHARS: 2000, MAX_SYSTEM_PROMPT_CHARS: 2000,
} as const; } as const;