Files
ragflow/web/src/pages/agent/form/components/query-variable.tsx
BitToby 73645e2f78 fix: preserve line breaks in prompt editor and add auto-save on blur (#12887)
Closes #12762 

### What problem does this PR solve?
**Line break issue in Agent prompt editor:**
- Text with blank lines in `system_prompt` or `user_prompt` would have
extra/fewer blank lines after save/reload or paste
- Root cause: Mismatch between Lexical editor's paragraph nodes (`\n\n`
separator) and line break nodes (`\n` separator)

**Auto-save issue:**
- Changes were only saved after 20-second debounce, causing data loss on
page refresh before timer completed

### Solution
1. **Line break fix**: Use `LineBreakNode` consistently for all line
breaks (typing Enter, paste, load)
2. **Auto-save**: Save immediately when prompt editor loses focus


[1.webm](https://github.com/user-attachments/assets/eb2c2428-54a3-4d4e-8037-6cc34a859b83)

### Type of change
- [x] Bug Fix (non-breaking change which fixes an issue)
2026-01-30 10:29:51 +08:00

91 lines
2.1 KiB
TypeScript

import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { ReactNode } from 'react';
import { useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { JsonSchemaDataType, VariableType } from '../../constant';
import {
BuildQueryVariableOptions,
useFilterQueryVariableOptionsByTypes,
} from '../../hooks/use-get-begin-query';
import { GroupedSelectWithSecondaryMenu } from './select-with-secondary-menu';
// Union type to support both JsonSchemaDataType and VariableType for filtering
type QueryVariableType = JsonSchemaDataType | VariableType;
type QueryVariableProps = {
name?: string;
types?: QueryVariableType[];
label?: ReactNode;
hideLabel?: boolean;
className?: string;
onChange?: (value: string) => void;
pureQuery?: boolean;
value?: string;
} & BuildQueryVariableOptions;
export function QueryVariable({
name = 'query',
types = [],
label,
hideLabel = false,
className,
onChange,
pureQuery = false,
value,
nodeIds = [],
variablesExceptOperatorOutputs,
}: QueryVariableProps) {
const { t } = useTranslation();
const form = useFormContext();
const finalOptions = useFilterQueryVariableOptionsByTypes({
types,
nodeIds,
variablesExceptOperatorOutputs,
});
const renderWidget = (
value?: string,
handleChange?: (value: string) => void,
) => (
<GroupedSelectWithSecondaryMenu
options={finalOptions}
value={value}
onChange={(val) => {
handleChange?.(val);
onChange?.(val);
}}
// allowClear
types={types}
></GroupedSelectWithSecondaryMenu>
);
if (pureQuery) {
renderWidget(value, onChange);
}
return (
<FormField
control={form.control}
name={name}
render={({ field }) => (
<FormItem className={className}>
{hideLabel || label || (
<FormLabel tooltip={t('flow.queryTip')}>
{t('flow.query')}
</FormLabel>
)}
<FormControl>{renderWidget(field.value, field.onChange)}</FormControl>
<FormMessage />
</FormItem>
)}
/>
);
}