update settings to use react query

This commit is contained in:
Lucas Oliveira 2025-09-18 13:33:56 -03:00
parent ba9ba0a36a
commit 8e267e1528
4 changed files with 230 additions and 143 deletions

23
frontend/lib/debounce.ts Normal file
View file

@ -0,0 +1,23 @@
import { useCallback, useRef } from "react";
export function useDebounce<T extends (...args: never[]) => void>(
callback: T,
delay: number,
): T {
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const debouncedCallback = useCallback(
(...args: Parameters<T>) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
callback(...args);
}, delay);
},
[callback, delay],
) as T;
return debouncedCallback;
}

View file

@ -1,12 +1,13 @@
import { import {
type UseMutationOptions,
useMutation, useMutation,
useQueryClient, useQueryClient,
UseMutationOptions,
} from "@tanstack/react-query"; } from "@tanstack/react-query";
interface UpdateFlowSettingVariables { interface UpdateFlowSettingVariables {
llm_model?: string; llm_model?: string;
system_prompt?: string; system_prompt?: string;
embedding_model?: string;
ocr?: boolean; ocr?: boolean;
picture_descriptions?: boolean; picture_descriptions?: boolean;
chunk_size?: number; chunk_size?: number;
@ -19,7 +20,11 @@ interface UpdateFlowSettingResponse {
export const useUpdateFlowSettingMutation = ( export const useUpdateFlowSettingMutation = (
options?: Omit< options?: Omit<
UseMutationOptions<UpdateFlowSettingResponse, Error, UpdateFlowSettingVariables>, UseMutationOptions<
UpdateFlowSettingResponse,
Error,
UpdateFlowSettingVariables
>,
"mutationFn" "mutationFn"
>, >,
) => { ) => {
@ -46,7 +51,7 @@ export const useUpdateFlowSettingMutation = (
return useMutation({ return useMutation({
mutationFn: updateFlowSetting, mutationFn: updateFlowSetting,
onSuccess: () => { onSettled: () => {
// Invalidate settings query to refetch updated data // Invalidate settings query to refetch updated data
queryClient.invalidateQueries({ queryKey: ["settings"] }); queryClient.invalidateQueries({ queryKey: ["settings"] });
}, },

View file

@ -0,0 +1,92 @@
import {
type UseQueryOptions,
useQuery,
useQueryClient,
} from "@tanstack/react-query";
interface AgentSettings {
llm_model?: string;
system_prompt?: string;
}
interface IngestSettings {
embedding_model?: string;
chunk_size?: number;
chunk_overlap?: number;
}
interface Settings {
flow_id?: string;
ingest_flow_id?: string;
langflow_edit_url?: string;
langflow_ingest_edit_url?: string;
langflow_public_url?: string;
agent?: AgentSettings;
ingest?: IngestSettings;
}
const DEFAULT_SETTINGS: Settings = {
flow_id: "1098eea1-6649-4e1d-aed1-b77249fb8dd0",
ingest_flow_id: "5488df7c-b93f-4f87-a446-b67028bc0813",
langflow_edit_url: "",
langflow_ingest_edit_url: "",
langflow_public_url: "",
agent: {
llm_model: "gpt-4",
system_prompt: "",
},
ingest: {
embedding_model: "text-embedding-ada-002",
chunk_size: 1000,
chunk_overlap: 200,
},
};
export const useGetSettingsQuery = (
options?: Omit<UseQueryOptions<Settings>, "queryKey" | "queryFn">,
) => {
const queryClient = useQueryClient();
function cancel() {
queryClient.removeQueries({ queryKey: ["settings"] });
}
async function getSettings(): Promise<Settings> {
try {
const response = await fetch("/api/settings");
if (response.ok) {
const settings = await response.json();
// Merge with defaults to ensure all properties exist
return {
...DEFAULT_SETTINGS,
...settings,
agent: {
...DEFAULT_SETTINGS.agent,
...settings.agent,
},
ingest: {
...DEFAULT_SETTINGS.ingest,
...settings.ingest,
},
};
} else {
console.error("Failed to fetch settings");
return DEFAULT_SETTINGS;
}
} catch (error) {
console.error("Error getting settings", error);
return DEFAULT_SETTINGS;
}
}
const queryResult = useQuery(
{
queryKey: ["settings"],
queryFn: getSettings,
...options,
},
queryClient,
);
return { ...queryResult, cancel };
};

View file

@ -2,7 +2,9 @@
import { Loader2, PlugZap, RefreshCw } from "lucide-react"; import { Loader2, PlugZap, RefreshCw } from "lucide-react";
import { useSearchParams } from "next/navigation"; import { useSearchParams } from "next/navigation";
import { Suspense, useCallback, useEffect, useRef, useState } from "react"; import { Suspense, useCallback, useEffect, useState } from "react";
import { useUpdateFlowSettingMutation } from "@/app/api/mutations/useUpdateFlowSettingMutation";
import { useGetSettingsQuery } from "@/app/api/queries/useGetSettingsQuery";
import { ConfirmationDialog } from "@/components/confirmation-dialog"; import { ConfirmationDialog } from "@/components/confirmation-dialog";
import { ProtectedRoute } from "@/components/protected-route"; import { ProtectedRoute } from "@/components/protected-route";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
@ -17,11 +19,17 @@ import {
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { useAuth } from "@/contexts/auth-context"; import { useAuth } from "@/contexts/auth-context";
import { useTask } from "@/contexts/task-context"; import { useTask } from "@/contexts/task-context";
import { useUpdateFlowSettingMutation } from "@/app/api/mutations/useUpdateFlowSettingMutation"; import { useDebounce } from "@/lib/debounce";
interface GoogleDriveFile { interface GoogleDriveFile {
id: string; id: string;
@ -55,11 +63,11 @@ interface Connector {
} }
interface SyncResult { interface SyncResult {
processed?: number; processed?: number;
added?: number; added?: number;
errors?: number; errors?: number;
skipped?: number; skipped?: number;
total?: number; total?: number;
} }
interface Connection { interface Connection {
@ -84,30 +92,13 @@ function KnowledgeSourcesPage() {
const [maxFiles, setMaxFiles] = useState<number>(10); const [maxFiles, setMaxFiles] = useState<number>(10);
const [syncAllFiles, setSyncAllFiles] = useState<boolean>(false); const [syncAllFiles, setSyncAllFiles] = useState<boolean>(false);
// Settings state // Only keep systemPrompt state since it needs manual save button
// Note: backend internal Langflow URL is not needed on the frontend
const [chatFlowId, setChatFlowId] = useState<string>(
"1098eea1-6649-4e1d-aed1-b77249fb8dd0",
);
const [ingestFlowId, setIngestFlowId] = useState<string>(
"5488df7c-b93f-4f87-a446-b67028bc0813",
);
const [langflowEditUrl, setLangflowEditUrl] = useState<string>("");
const [langflowIngestEditUrl, setLangflowIngestEditUrl] = useState<string>("");
const [publicLangflowUrl, setPublicLangflowUrl] = useState<string>("");
// Agent Behavior state
const [selectedModel, setSelectedModel] = useState<string>("gpt-4");
const [systemPrompt, setSystemPrompt] = useState<string>(""); const [systemPrompt, setSystemPrompt] = useState<string>("");
// Knowledge Ingest state // Fetch settings using React Query
const [selectedEmbeddingModel, setSelectedEmbeddingModel] = useState<string>("text-embedding-ada-002"); const { data: settings = {} } = useGetSettingsQuery({
const [chunkSize, setChunkSize] = useState<number>(1000); enabled: isAuthenticated,
const [chunkOverlap, setChunkOverlap] = useState<number>(200); });
// Debounce refs for chunk settings
const chunkSizeTimeoutRef = useRef<NodeJS.Timeout>();
const chunkOverlapTimeoutRef = useRef<NodeJS.Timeout>();
// Mutations // Mutations
const updateFlowSettingMutation = useUpdateFlowSettingMutation({ const updateFlowSettingMutation = useUpdateFlowSettingMutation({
@ -119,52 +110,23 @@ function KnowledgeSourcesPage() {
}, },
}); });
// Debounced update function
const debouncedUpdate = useDebounce(
(variables: Parameters<typeof updateFlowSettingMutation.mutate>[0]) => {
updateFlowSettingMutation.mutate(variables);
},
500,
);
// Fetch settings from backend // Sync system prompt state with settings data
const fetchSettings = useCallback(async () => { useEffect(() => {
try { if (settings.agent?.system_prompt) {
const response = await fetch("/api/settings"); setSystemPrompt(settings.agent.system_prompt);
if (response.ok) {
const settings = await response.json();
if (settings.flow_id) {
setChatFlowId(settings.flow_id);
}
if (settings.ingest_flow_id) {
setIngestFlowId(settings.ingest_flow_id);
}
if (settings.langflow_edit_url) {
setLangflowEditUrl(settings.langflow_edit_url);
}
if (settings.langflow_ingest_edit_url) {
setLangflowIngestEditUrl(settings.langflow_ingest_edit_url);
}
if (settings.langflow_public_url) {
setPublicLangflowUrl(settings.langflow_public_url);
}
if (settings.agent?.llm_model) {
setSelectedModel(settings.agent.llm_model);
}
if (settings.agent?.system_prompt) {
setSystemPrompt(settings.agent.system_prompt);
}
if (settings.ingest?.embedding_model) {
setSelectedEmbeddingModel(settings.ingest.embedding_model);
}
if (settings.ingest?.chunk_size) {
setChunkSize(settings.ingest.chunk_size);
}
if (settings.ingest?.chunk_overlap !== undefined) {
setChunkOverlap(settings.ingest.chunk_overlap);
}
}
} catch (error) {
console.error("Failed to fetch settings:", error);
} }
}, []); }, [settings.agent?.system_prompt]);
// Update model selection immediately // Update model selection immediately
const handleModelChange = (newModel: string) => { const handleModelChange = (newModel: string) => {
setSelectedModel(newModel);
updateFlowSettingMutation.mutate({ llm_model: newModel }); updateFlowSettingMutation.mutate({ llm_model: newModel });
}; };
@ -175,44 +137,23 @@ function KnowledgeSourcesPage() {
// Update embedding model selection immediately // Update embedding model selection immediately
const handleEmbeddingModelChange = (newModel: string) => { const handleEmbeddingModelChange = (newModel: string) => {
setSelectedEmbeddingModel(newModel);
updateFlowSettingMutation.mutate({ embedding_model: newModel }); updateFlowSettingMutation.mutate({ embedding_model: newModel });
}; };
// Update chunk size setting with debounce // Update chunk size setting with debounce
const handleChunkSizeChange = (value: string) => { const handleChunkSizeChange = (value: string) => {
const numValue = Math.max(0, parseInt(value) || 0); const numValue = Math.max(0, parseInt(value) || 0);
setChunkSize(numValue); debouncedUpdate({ chunk_size: numValue });
// Clear existing timeout
if (chunkSizeTimeoutRef.current) {
clearTimeout(chunkSizeTimeoutRef.current);
}
// Set new timeout for API call
chunkSizeTimeoutRef.current = setTimeout(() => {
updateFlowSettingMutation.mutate({ chunk_size: numValue });
}, 500);
}; };
// Update chunk overlap setting with debounce // Update chunk overlap setting with debounce
const handleChunkOverlapChange = (value: string) => { const handleChunkOverlapChange = (value: string) => {
const numValue = Math.max(0, parseInt(value) || 0); const numValue = Math.max(0, parseInt(value) || 0);
setChunkOverlap(numValue); debouncedUpdate({ chunk_overlap: numValue });
// Clear existing timeout
if (chunkOverlapTimeoutRef.current) {
clearTimeout(chunkOverlapTimeoutRef.current);
}
// Set new timeout for API call
chunkOverlapTimeoutRef.current = setTimeout(() => {
updateFlowSettingMutation.mutate({ chunk_overlap: numValue });
}, 500);
}; };
// Helper function to get connector icon // Helper function to get connector icon
const getConnectorIcon = (iconName: string) => { const getConnectorIcon = useCallback((iconName: string) => {
const iconMap: { [key: string]: React.ReactElement } = { const iconMap: { [key: string]: React.ReactElement } = {
"google-drive": ( "google-drive": (
<div className="w-8 h-8 bg-blue-600 rounded flex items-center justify-center text-white font-bold leading-none shrink-0"> <div className="w-8 h-8 bg-blue-600 rounded flex items-center justify-center text-white font-bold leading-none shrink-0">
@ -237,7 +178,7 @@ function KnowledgeSourcesPage() {
</div> </div>
) )
); );
}; }, []);
// Connector functions // Connector functions
const checkConnectorStatuses = useCallback(async () => { const checkConnectorStatuses = useCallback(async () => {
@ -293,7 +234,7 @@ function KnowledgeSourcesPage() {
} catch (error) { } catch (error) {
console.error("Failed to check connector statuses:", error); console.error("Failed to check connector statuses:", error);
} }
}, []); }, [getConnectorIcon]);
const handleConnect = async (connector: Connector) => { const handleConnect = async (connector: Connector) => {
setIsConnecting(connector.id); setIsConnecting(connector.id);
@ -434,13 +375,6 @@ function KnowledgeSourcesPage() {
} }
}; };
// Fetch settings on mount when authenticated
useEffect(() => {
if (isAuthenticated) {
fetchSettings();
}
}, [isAuthenticated, fetchSettings]);
// Check connector status on mount and when returning from OAuth // Check connector status on mount and when returning from OAuth
useEffect(() => { useEffect(() => {
if (isAuthenticated) { if (isAuthenticated) {
@ -483,29 +417,24 @@ function KnowledgeSourcesPage() {
} }
}, [tasks, prevTasks]); }, [tasks, prevTasks]);
// Cleanup timeouts on unmount const handleEditInLangflow = (
useEffect(() => { flowType: "chat" | "ingest",
return () => { closeDialog: () => void,
if (chunkSizeTimeoutRef.current) { ) => {
clearTimeout(chunkSizeTimeoutRef.current);
}
if (chunkOverlapTimeoutRef.current) {
clearTimeout(chunkOverlapTimeoutRef.current);
}
};
}, []);
const handleEditInLangflow = (flowType: "chat" | "ingest", closeDialog: () => void) => {
// Select the appropriate flow ID and edit URL based on flow type // Select the appropriate flow ID and edit URL based on flow type
const targetFlowId = flowType === "ingest" ? ingestFlowId : chatFlowId; const targetFlowId =
const editUrl = flowType === "ingest" ? langflowIngestEditUrl : langflowEditUrl; flowType === "ingest" ? settings.ingest_flow_id : settings.flow_id;
const editUrl =
flowType === "ingest"
? settings.langflow_ingest_edit_url
: settings.langflow_edit_url;
const derivedFromWindow = const derivedFromWindow =
typeof window !== "undefined" typeof window !== "undefined"
? `${window.location.protocol}//${window.location.hostname}:7860` ? `${window.location.protocol}//${window.location.hostname}:7860`
: ""; : "";
const base = ( const base = (
publicLangflowUrl || settings.langflow_public_url ||
derivedFromWindow || derivedFromWindow ||
"http://localhost:7860" "http://localhost:7860"
).replace(/\/$/, ""); ).replace(/\/$/, "");
@ -575,7 +504,9 @@ function KnowledgeSourcesPage() {
height="22" height="22"
viewBox="0 0 24 22" viewBox="0 0 24 22"
className="h-4 w-4 mr-2" className="h-4 w-4 mr-2"
aria-label="Langflow icon"
> >
<title>Langflow icon</title>
<path <path
fill="currentColor" fill="currentColor"
d="M13.0486 0.462158H9.75399C9.44371 0.462158 9.14614 0.586082 8.92674 0.806667L4.03751 5.72232C3.81811 5.9429 3.52054 6.06682 3.21026 6.06682H1.16992C0.511975 6.06682 -0.0165756 6.61212 0.000397655 7.2734L0.0515933 9.26798C0.0679586 9.90556 0.586745 10.4139 1.22111 10.4139H3.59097C3.90124 10.4139 4.19881 10.2899 4.41821 10.0694L9.34823 5.11269C9.56763 4.89211 9.8652 4.76818 10.1755 4.76818H13.0486C13.6947 4.76818 14.2185 4.24157 14.2185 3.59195V1.63839C14.2185 0.988773 13.6947 0.462158 13.0486 0.462158Z" d="M13.0486 0.462158H9.75399C9.44371 0.462158 9.14614 0.586082 8.92674 0.806667L4.03751 5.72232C3.81811 5.9429 3.52054 6.06682 3.21026 6.06682H1.16992C0.511975 6.06682 -0.0165756 6.61212 0.000397655 7.2734L0.0515933 9.26798C0.0679586 9.90556 0.586745 10.4139 1.22111 10.4139H3.59097C3.90124 10.4139 4.19881 10.2899 4.41821 10.0694L9.34823 5.11269C9.56763 4.89211 9.8652 4.76818 10.1755 4.76818H13.0486C13.6947 4.76818 14.2185 4.24157 14.2185 3.59195V1.63839C14.2185 0.988773 13.6947 0.462158 13.0486 0.462158Z"
@ -595,7 +526,9 @@ function KnowledgeSourcesPage() {
title="Edit Agent flow in Langflow" title="Edit Agent flow in Langflow"
description="You're entering Langflow. You can edit the Agent flow and other underlying flows. Manual changes to components, wiring, or I/O can break this experience." description="You're entering Langflow. You can edit the Agent flow and other underlying flows. Manual changes to components, wiring, or I/O can break this experience."
confirmText="Proceed" confirmText="Proceed"
onConfirm={(closeDialog) => handleEditInLangflow("chat", closeDialog)} onConfirm={(closeDialog) =>
handleEditInLangflow("chat", closeDialog)
}
/> />
</div> </div>
</div> </div>
@ -606,7 +539,10 @@ function KnowledgeSourcesPage() {
<Label htmlFor="model-select" className="text-base font-medium"> <Label htmlFor="model-select" className="text-base font-medium">
Model Model
</Label> </Label>
<Select value={selectedModel} onValueChange={handleModelChange}> <Select
value={settings.agent?.llm_model || "gpt-4"}
onValueChange={handleModelChange}
>
<SelectTrigger id="model-select"> <SelectTrigger id="model-select">
<SelectValue placeholder="Select a model" /> <SelectValue placeholder="Select a model" />
</SelectTrigger> </SelectTrigger>
@ -615,7 +551,9 @@ function KnowledgeSourcesPage() {
<SelectItem value="gpt-4-turbo">GPT-4 Turbo</SelectItem> <SelectItem value="gpt-4-turbo">GPT-4 Turbo</SelectItem>
<SelectItem value="gpt-3.5-turbo">GPT-3.5 Turbo</SelectItem> <SelectItem value="gpt-3.5-turbo">GPT-3.5 Turbo</SelectItem>
<SelectItem value="claude-3-opus">Claude 3 Opus</SelectItem> <SelectItem value="claude-3-opus">Claude 3 Opus</SelectItem>
<SelectItem value="claude-3-sonnet">Claude 3 Sonnet</SelectItem> <SelectItem value="claude-3-sonnet">
Claude 3 Sonnet
</SelectItem>
<SelectItem value="claude-3-haiku">Claude 3 Haiku</SelectItem> <SelectItem value="claude-3-haiku">Claude 3 Haiku</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
@ -682,7 +620,9 @@ function KnowledgeSourcesPage() {
height="22" height="22"
viewBox="0 0 24 22" viewBox="0 0 24 22"
className="h-4 w-4 mr-2" className="h-4 w-4 mr-2"
aria-label="Langflow icon"
> >
<title>Langflow icon</title>
<path <path
fill="currentColor" fill="currentColor"
d="M13.0486 0.462158H9.75399C9.44371 0.462158 9.14614 0.586082 8.92674 0.806667L4.03751 5.72232C3.81811 5.9429 3.52054 6.06682 3.21026 6.06682H1.16992C0.511975 6.06682 -0.0165756 6.61212 0.000397655 7.2734L0.0515933 9.26798C0.0679586 9.90556 0.586745 10.4139 1.22111 10.4139H3.59097C3.90124 10.4139 4.19881 10.2899 4.41821 10.0694L9.34823 5.11269C9.56763 4.89211 9.8652 4.76818 10.1755 4.76818H13.0486C13.6947 4.76818 14.2185 4.24157 14.2185 3.59195V1.63839C14.2185 0.988773 13.6947 0.462158 13.0486 0.462158Z" d="M13.0486 0.462158H9.75399C9.44371 0.462158 9.14614 0.586082 8.92674 0.806667L4.03751 5.72232C3.81811 5.9429 3.52054 6.06682 3.21026 6.06682H1.16992C0.511975 6.06682 -0.0165756 6.61212 0.000397655 7.2734L0.0515933 9.26798C0.0679586 9.90556 0.586745 10.4139 1.22111 10.4139H3.59097C3.90124 10.4139 4.19881 10.2899 4.41821 10.0694L9.34823 5.11269C9.56763 4.89211 9.8652 4.76818 10.1755 4.76818H13.0486C13.6947 4.76818 14.2185 4.24157 14.2185 3.59195V1.63839C14.2185 0.988773 13.6947 0.462158 13.0486 0.462158Z"
@ -702,7 +642,9 @@ function KnowledgeSourcesPage() {
title="Edit Ingest flow in Langflow" title="Edit Ingest flow in Langflow"
description="You're entering Langflow. You can edit the Ingest flow and other underlying flows. Manual changes to components, wiring, or I/O can break this experience." description="You're entering Langflow. You can edit the Ingest flow and other underlying flows. Manual changes to components, wiring, or I/O can break this experience."
confirmText="Proceed" confirmText="Proceed"
onConfirm={(closeDialog) => handleEditInLangflow("ingest", closeDialog)} onConfirm={(closeDialog) =>
handleEditInLangflow("ingest", closeDialog)
}
/> />
</div> </div>
</div> </div>
@ -710,19 +652,37 @@ function KnowledgeSourcesPage() {
<CardContent> <CardContent>
<div className="space-y-6"> <div className="space-y-6">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="embedding-model-select" className="text-base font-medium"> <Label
htmlFor="embedding-model-select"
className="text-base font-medium"
>
Embedding Model Embedding Model
</Label> </Label>
<Select value={selectedEmbeddingModel} onValueChange={handleEmbeddingModelChange}> <Select
value={
settings.ingest?.embedding_model || "text-embedding-ada-002"
}
onValueChange={handleEmbeddingModelChange}
>
<SelectTrigger id="embedding-model-select"> <SelectTrigger id="embedding-model-select">
<SelectValue placeholder="Select an embedding model" /> <SelectValue placeholder="Select an embedding model" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="text-embedding-ada-002">text-embedding-ada-002</SelectItem> <SelectItem value="text-embedding-ada-002">
<SelectItem value="text-embedding-3-small">text-embedding-3-small</SelectItem> text-embedding-ada-002
<SelectItem value="text-embedding-3-large">text-embedding-3-large</SelectItem> </SelectItem>
<SelectItem value="all-MiniLM-L6-v2">all-MiniLM-L6-v2</SelectItem> <SelectItem value="text-embedding-3-small">
<SelectItem value="all-mpnet-base-v2">all-mpnet-base-v2</SelectItem> text-embedding-3-small
</SelectItem>
<SelectItem value="text-embedding-3-large">
text-embedding-3-large
</SelectItem>
<SelectItem value="all-MiniLM-L6-v2">
all-MiniLM-L6-v2
</SelectItem>
<SelectItem value="all-mpnet-base-v2">
all-mpnet-base-v2
</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
@ -736,17 +696,22 @@ function KnowledgeSourcesPage() {
id="chunk-size" id="chunk-size"
type="number" type="number"
min="1" min="1"
value={chunkSize} defaultValue={settings.ingest?.chunk_size || 1000}
onChange={(e) => handleChunkSizeChange(e.target.value)} onChange={(e) => handleChunkSizeChange(e.target.value)}
className="w-full pr-20" className="w-full pr-20"
/> />
<div className="absolute inset-y-0 right-0 flex items-center pr-8 pointer-events-none"> <div className="absolute inset-y-0 right-0 flex items-center pr-8 pointer-events-none">
<span className="text-sm text-muted-foreground">characters</span> <span className="text-sm text-muted-foreground">
characters
</span>
</div> </div>
</div> </div>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="chunk-overlap" className="text-base font-medium"> <Label
htmlFor="chunk-overlap"
className="text-base font-medium"
>
Chunk Overlap Chunk Overlap
</Label> </Label>
<div className="relative"> <div className="relative">
@ -754,12 +719,14 @@ function KnowledgeSourcesPage() {
id="chunk-overlap" id="chunk-overlap"
type="number" type="number"
min="0" min="0"
value={chunkOverlap} defaultValue={settings.ingest?.chunk_overlap || 200}
onChange={(e) => handleChunkOverlapChange(e.target.value)} onChange={(e) => handleChunkOverlapChange(e.target.value)}
className="w-full pr-20" className="w-full pr-20"
/> />
<div className="absolute inset-y-0 right-0 flex items-center pr-8 pointer-events-none"> <div className="absolute inset-y-0 right-0 flex items-center pr-8 pointer-events-none">
<span className="text-sm text-muted-foreground">characters</span> <span className="text-sm text-muted-foreground">
characters
</span>
</div> </div>
</div> </div>
</div> </div>