Fix: Editing an empty response in the retrieval operator will cause the focus to shift to the metadata input box. (#14253)

### What problem does this PR solve?

Fix: Editing an empty response in the retrieval operator will cause the
focus to shift to the metadata input box.

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
balibabu
2026-04-21 16:19:55 +08:00
committed by GitHub
parent 05c39b90a8
commit a2bea30749
2 changed files with 160 additions and 126 deletions

View File

@@ -5,14 +5,16 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview
RAGFlow is an open-source RAG (Retrieval-Augmented Generation) engine based on deep document understanding. It's a full-stack application with:
- Python backend (Flask-based API server)
- React/TypeScript frontend (built with UmiJS)
- React/TypeScript frontend (built with vitejs)
- Microservices architecture with Docker deployment
- Multiple data stores (MySQL, Elasticsearch/Infinity, Redis, MinIO)
## Architecture
### Backend (`/api/`)
- **Main Server**: `api/ragflow_server.py` - Flask application entry point
- **Apps**: Modular Flask blueprints in `api/apps/` for different functionalities:
- `kb_app.py` - Knowledge base management
@@ -24,25 +26,29 @@ RAGFlow is an open-source RAG (Retrieval-Augmented Generation) engine based on d
- **Models**: Database models in `api/db/db_models.py`
### Core Processing (`/rag/`)
- **Document Processing**: `deepdoc/` - PDF parsing, OCR, layout analysis
- **LLM Integration**: `rag/llm/` - Model abstractions for chat, embedding, reranking
- **RAG Pipeline**: `rag/flow/` - Chunking, parsing, tokenization
- **Graph RAG**: `rag/graphrag/` - Knowledge graph construction and querying
### Agent System (`/agent/`)
- **Components**: Modular workflow components (LLM, retrieval, categorize, etc.)
- **Templates**: Pre-built agent workflows in `agent/templates/`
- **Tools**: External API integrations (Tavily, Wikipedia, SQL execution, etc.)
### Frontend (`/web/`)
- React/TypeScript with UmiJS framework
- Ant Design + shadcn/ui components
- React/TypeScript with vitejs framework
- shadcn/ui components
- State management with Zustand
- Tailwind CSS for styling
## Common Development Commands
### Backend Development
```bash
# Install Python dependencies
uv sync --python 3.12 --all-extras
@@ -66,6 +72,7 @@ ruff format
```
### Frontend Development
```bash
cd web
npm install
@@ -76,6 +83,7 @@ npm run test # Jest tests
```
### Docker Development
```bash
# Full stack with Docker
cd docker
@@ -104,6 +112,7 @@ docker build --platform linux/amd64 -f Dockerfile -t infiniflow/ragflow:nightly
## Database Engines
RAGFlow supports switching between Elasticsearch (default) and Infinity:
- Set `DOC_ENGINE=infinity` in `docker/.env` to use Infinity
- Requires container restart: `docker compose down -v && docker compose up -d`
@@ -114,3 +123,12 @@ RAGFlow supports switching between Elasticsearch (default) and Infinity:
- Docker & Docker Compose
- uv package manager
- 16GB+ RAM, 50GB+ disk space
1. Think before acting. Read existing files before writing code.
2. Be concise in output but thorough in reasoning.
3. Prefer editing over rewriting whole files.
4. Do not re-read files you have already read.
5. Test your code before declaring done.
6. No sycophantic openers or closing fluff.
7. Keep solutions simple and direct.
8. User instructions always override this file.

View File

@@ -28,6 +28,140 @@ import { Card, CardContent } from '../ui/card';
import { InputSelect } from '../ui/input-select';
import { RAGFlowSelect } from '../ui/select';
type ConditionCardsProps = {
fieldName: string;
index: number;
name: string;
remove: (index: number) => void;
switchOperatorOptions: ReturnType<typeof useBuildSwitchOperatorOptions>;
metadata: ReturnType<typeof useFetchKnowledgeMetadata>;
canReference?: boolean;
};
function ConditionCards({
fieldName,
index,
name,
remove,
switchOperatorOptions,
metadata,
canReference,
}: ConditionCardsProps) {
const { t } = useTranslation();
const form = useFormContext();
const op = useWatch({ name: `${name}.${index}.op` });
const key = useWatch({ name: fieldName });
const valueOptions = useMemo(() => {
if (!key || !metadata?.data || !metadata?.data[key]) return [];
if (typeof metadata?.data[key] === 'object') {
return Object.keys(metadata?.data[key]).map((item: string) => ({
value: item,
label: item,
}));
}
return [];
}, [key, metadata?.data]);
const handleChangeOp = useCallback(
(value: string) => {
form.setValue(`${name}.${index}.op`, value);
if (!['in', 'not in'].includes(value) && !['in', 'not in'].includes(op)) {
return;
}
if (value === 'in' || value === 'not in') {
form.setValue(`${name}.${index}.value`, []);
} else {
form.setValue(`${name}.${index}.value`, '');
}
},
[form, index, op, name],
);
return (
<div className="flex gap-1">
<Card
className={cn(
'relative bg-transparent border-input-border border flex-1 min-w-0',
)}
>
<section className="p-2 bg-bg-card flex justify-between items-center">
<FormField
control={form.control}
name={fieldName}
render={({ field }) => (
<FormItem className="flex-1 min-w-0">
<FormControl>
<Input
{...field}
placeholder={t('common.pleaseInput')}
></Input>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex items-center">
<Separator orientation="vertical" className="h-2.5" />
<FormField
control={form.control}
name={`${name}.${index}.op`}
render={({ field }) => (
<FormItem>
<FormControl>
<RAGFlowSelect
{...field}
onChange={(value) => {
handleChangeOp(value);
}}
options={switchOperatorOptions}
onlyShowSelectedIcon
triggerClassName="w-30 bg-transparent border-none"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</section>
<CardContent className="p-4 ">
<FormField
control={form.control}
name={`${name}.${index}.value`}
render={({ field: valueField }) => {
return (
<FormItem>
<FormControl>
{canReference ? (
<PromptEditor
{...valueField}
multiLine={false}
showToolbar={false}
></PromptEditor>
) : (
<InputSelect
placeholder={t('common.pleaseInput')}
{...valueField}
options={valueOptions}
className="w-full"
multi={op === 'in' || op === 'not in'}
/>
)}
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
</CardContent>
</Card>
<Button variant={'ghost'} onClick={() => remove(index)}>
<X />
</Button>
</div>
);
}
export function MetadataFilterConditions({
kbIds,
prefix = '',
@@ -64,129 +198,6 @@ export function MetadataFilterConditions({
[append, fields.length, form, logic],
);
function ConditionCards({
fieldName,
index,
}: {
fieldName: string;
index: number;
}) {
const { t } = useTranslation();
const form = useFormContext();
const op = useWatch({ name: `${name}.${index}.op` });
const key = useWatch({ name: fieldName });
const valueOptions = useMemo(() => {
if (!key || !metadata?.data || !metadata?.data[key]) return [];
if (typeof metadata?.data[key] === 'object') {
return Object.keys(metadata?.data[key]).map((item: string) => ({
value: item,
label: item,
}));
}
return [];
}, [key]);
const handleChangeOp = useCallback(
(value: string) => {
form.setValue(`${name}.${index}.op`, value);
if (
!['in', 'not in'].includes(value) &&
!['in', 'not in'].includes(op)
) {
return;
}
if (value === 'in' || value === 'not in') {
form.setValue(`${name}.${index}.value`, []);
} else {
form.setValue(`${name}.${index}.value`, '');
}
},
[form, index, op],
);
return (
<div className="flex gap-1">
<Card
className={cn(
'relative bg-transparent border-input-border border flex-1 min-w-0',
)}
>
<section className="p-2 bg-bg-card flex justify-between items-center">
<FormField
control={form.control}
name={fieldName}
render={({ field }) => (
<FormItem className="flex-1 min-w-0">
<FormControl>
<Input
{...field}
placeholder={t('common.pleaseInput')}
></Input>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex items-center">
<Separator orientation="vertical" className="h-2.5" />
<FormField
control={form.control}
name={`${name}.${index}.op`}
render={({ field }) => (
<FormItem>
<FormControl>
<RAGFlowSelect
{...field}
onChange={(value) => {
handleChangeOp(value);
}}
options={switchOperatorOptions}
onlyShowSelectedIcon
triggerClassName="w-30 bg-transparent border-none"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</section>
<CardContent className="p-4 ">
<FormField
control={form.control}
name={`${name}.${index}.value`}
render={({ field: valueField }) => {
return (
<FormItem>
<FormControl>
{canReference ? (
<PromptEditor
{...valueField}
multiLine={false}
showToolbar={false}
></PromptEditor>
) : (
<InputSelect
placeholder={t('common.pleaseInput')}
{...valueField}
options={valueOptions}
className="w-full"
multi={op === 'in' || op === 'not in'}
/>
)}
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
</CardContent>
</Card>
<Button variant={'ghost'} onClick={() => remove(index)}>
<X />
</Button>
</div>
);
}
return (
<section className="flex flex-col gap-2">
<div className="flex items-center justify-between">
@@ -218,6 +229,11 @@ export function MetadataFilterConditions({
key={field.id}
fieldName={typeField}
index={index}
name={name}
remove={remove}
switchOperatorOptions={switchOperatorOptions}
metadata={metadata}
canReference={canReference}
/>
);
})}