diff --git a/frontend/src/components/PluginSchemaForm.tsx b/frontend/src/components/PluginSchemaForm.tsx index 290cad9..73b40a9 100644 --- a/frontend/src/components/PluginSchemaForm.tsx +++ b/frontend/src/components/PluginSchemaForm.tsx @@ -17,6 +17,10 @@ interface PluginSchemaFormProps { isPending?: boolean; isError?: boolean; isSuccess?: boolean; + /** When true, the built-in save button row is hidden (caller renders its own). */ + noSaveButton?: boolean; + /** Expose current form state to the parent via callback on every change. */ + onChange?: (values: Record) => void; } function Toggle({ checked, onChange }: { checked: boolean; onChange: (v: boolean) => void }) { @@ -48,6 +52,8 @@ export default function PluginSchemaForm({ isPending, isError, isSuccess, + noSaveButton, + onChange, }: PluginSchemaFormProps) { const [form, setForm] = useState>(values); @@ -56,7 +62,9 @@ export default function PluginSchemaForm({ }, [values]); const setField = (key: string, value: unknown) => { - setForm((prev) => ({ ...prev, [key]: value })); + const next = { ...form, [key]: value }; + setForm(next); + onChange?.(next); }; return ( @@ -103,17 +111,19 @@ export default function PluginSchemaForm({ ))} -
- - {isError && ( - Failed to save. Please try again. - )} - {isSuccess && !isPending && ( - Saved successfully. - )} -
+ {!noSaveButton && ( +
+ + {isError && ( + Failed to save. Please try again. + )} + {isSuccess && !isPending && ( + Saved successfully. + )} +
+ )} ); } diff --git a/frontend/src/pages/DocServiceSettingsPage.tsx b/frontend/src/pages/DocServiceSettingsPage.tsx index 7183a13..21ae89e 100644 --- a/frontend/src/pages/DocServiceSettingsPage.tsx +++ b/frontend/src/pages/DocServiceSettingsPage.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { getDocumentLimits, @@ -8,6 +8,7 @@ import { getPluginManifest, } from "../api/client"; import PluginSchemaForm from "../components/PluginSchemaForm"; +import { Button } from "@/components/ui/button"; const inputStyle: React.CSSProperties = { width: 120, @@ -20,133 +21,130 @@ const inputStyle: React.CSSProperties = { color: "rgb(var(--color-text-primary))", }; -function Section({ title, children }: { title: string; children: React.ReactNode }) { +function Section({ title, description, children }: { title: string; description?: string; children: React.ReactNode }) { return (
-

{title}

+

{title}

+ {description && ( +

+ {description} +

+ )} {children}
); } -function UploadLimitsSection() { - const { data: rawSettings, isLoading } = useQuery({ +export default function DocServiceSettingsPage() { + const queryClient = useQueryClient(); + + // ── Upload limits ──────────────────────────────────────────────────────────── + const { data: limitsData, isLoading: limitsLoading } = useQuery({ queryKey: ["docLimits"], queryFn: getDocumentLimits, }); - const [maxPdfMb, setMaxPdfMb] = useState(20); - useEffect(() => { - if (!rawSettings) return; - const s = rawSettings as Record; + if (!limitsData) return; + const s = limitsData as Record; const docs = s.documents as Record | undefined; if (typeof docs?.max_pdf_bytes === "number") { setMaxPdfMb(Math.round((docs.max_pdf_bytes as number) / (1024 * 1024))); } - }, [rawSettings]); - - const limitsMut = useMutation({ - mutationFn: (mb: number) => updateDocumentLimits(mb), - }); - - if (isLoading) return
Loading…
; - - return ( -
-
- - setMaxPdfMb(Number(e.target.value))} - style={inputStyle} - /> -
- - {limitsMut.isSuccess && ( -

Limits saved.

- )} - {limitsMut.isError && ( -

Failed to save.

- )} -
- ); -} - -function WatchDirectorySection() { - const queryClient = useQueryClient(); + }, [limitsData]); + // ── Watch directory ────────────────────────────────────────────────────────── const { data: manifest, isLoading: manifestLoading } = useQuery({ queryKey: ["plugin-manifest", "doc-service"], queryFn: () => getPluginManifest("doc-service"), retry: false, }); - - const { data: settingsValues, isLoading: settingsLoading } = useQuery({ + const { data: watchData, isLoading: watchLoading } = useQuery({ queryKey: ["plugin-settings", "doc-service"], queryFn: () => getPluginSettings("doc-service"), retry: false, }); - const updateMut = useMutation({ + // Ref so PluginSchemaForm can push its current values to us on change + const watchValuesRef = useRef>({}); + useEffect(() => { + if (watchData) watchValuesRef.current = watchData as Record; + }, [watchData]); + + // ── Mutations ──────────────────────────────────────────────────────────────── + const limitsMut = useMutation({ + mutationFn: (mb: number) => updateDocumentLimits(mb), + }); + const watchMut = useMutation({ mutationFn: (values: Record) => updatePluginSettings("doc-service", values), onSuccess: (data) => { queryClient.setQueryData(["plugin-settings", "doc-service"], data); }, }); - if (manifestLoading || settingsLoading) return
Loading…
; + const isPending = limitsMut.isPending || watchMut.isPending; + const isError = limitsMut.isError || watchMut.isError; + const isSuccess = limitsMut.isSuccess && watchMut.isSuccess; - if (!manifest?.settings_schema || !settingsValues) { - return ( -
- Watch directory settings unavailable. Ensure the doc-service is running. -
- ); + const handleSave = () => { + limitsMut.mutate(maxPdfMb); + if (manifest?.settings_schema) { + watchMut.mutate(watchValuesRef.current); + } + }; + + const isLoading = limitsLoading || manifestLoading || watchLoading; + + if (isLoading) { + return
Loading…
; } - return ( -
-

- Automatically ingest PDF files dropped into the watched directory. Subfolders are mapped to document categories. -

- } - onSave={(values) => updateMut.mutate(values)} - isPending={updateMut.isPending} - isError={updateMut.isError} - isSuccess={updateMut.isSuccess} - /> -
- ); -} - -export default function DocServiceSettingsPage() { return (

Documents — Settings

- - + +
+
+ + setMaxPdfMb(Number(e.target.value))} + style={inputStyle} + /> +
+
+ + {manifest?.settings_schema && watchData ? ( +
+ } + onSave={() => {}} + onChange={(values) => { watchValuesRef.current = values; }} + noSaveButton + /> +
+ ) : null} + +
+ + {isError && ( + Failed to save. Please try again. + )} + {isSuccess && !isPending && ( + Saved successfully. + )} +
); }