diff --git a/web/src/components/config-form/section-configs/review.ts b/web/src/components/config-form/section-configs/review.ts index 7d7a84756..3bd446e31 100644 --- a/web/src/components/config-form/section-configs/review.ts +++ b/web/src/components/config-form/section-configs/review.ts @@ -29,7 +29,7 @@ const review: SectionConfigOverrides = { }, genai: { additional_concerns: { - "ui:widget": "textarea", + "ui:widget": "textareaArray", "ui:options": { size: "full", }, diff --git a/web/src/components/config-form/theme/frigateTheme.ts b/web/src/components/config-form/theme/frigateTheme.ts index 5497e35b7..9bee54529 100644 --- a/web/src/components/config-form/theme/frigateTheme.ts +++ b/web/src/components/config-form/theme/frigateTheme.ts @@ -30,6 +30,7 @@ import { CameraPathWidget } from "./widgets/CameraPathWidget"; import { OptionalFieldWidget } from "./widgets/OptionalFieldWidget"; import { SemanticSearchModelWidget } from "./widgets/SemanticSearchModelWidget"; import { OnvifProfileWidget } from "./widgets/OnvifProfileWidget"; +import { TextareaArrayWidget } from "./widgets/TextareaArrayWidget"; import { FieldTemplate } from "./templates/FieldTemplate"; import { ObjectFieldTemplate } from "./templates/ObjectFieldTemplate"; @@ -81,6 +82,7 @@ export const frigateTheme: FrigateTheme = { optionalField: OptionalFieldWidget, semanticSearchModel: SemanticSearchModelWidget, onvifProfile: OnvifProfileWidget, + textareaArray: TextareaArrayWidget, }, templates: { FieldTemplate: FieldTemplate as React.ComponentType, diff --git a/web/src/components/config-form/theme/widgets/TextareaArrayWidget.tsx b/web/src/components/config-form/theme/widgets/TextareaArrayWidget.tsx new file mode 100644 index 000000000..2f8e379c8 --- /dev/null +++ b/web/src/components/config-form/theme/widgets/TextareaArrayWidget.tsx @@ -0,0 +1,50 @@ +// Textarea Array Widget - displays an array of strings as a multiline textarea +// Each line in the textarea corresponds to one item in the array. +import type { WidgetProps } from "@rjsf/utils"; +import { Textarea } from "@/components/ui/textarea"; +import { cn } from "@/lib/utils"; +import { getSizedFieldClassName } from "../utils"; +import { useCallback } from "react"; + +export function TextareaArrayWidget(props: WidgetProps) { + const { id, value, disabled, readonly, onChange, onBlur, onFocus, schema, options } = + props; + + // Convert array to newline-separated text for display + let textValue = ""; + if (Array.isArray(value) && value.length > 0) { + textValue = value.join("\n"); + } else if (typeof value === "string") { + textValue = value; + } + + const fieldClassName = getSizedFieldClassName(options, "md"); + + const handleChange = useCallback( + (e: React.ChangeEvent) => { + const text = e.target.value; + if (text === "") { + onChange([]); + return; + } + // Split by newlines and filter out empty lines + const items = text.split("\n").filter((line) => line.trim() !== ""); + onChange(items); + }, + [onChange], + ); + + return ( +