mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-03 17:21:59 +08:00
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:
24
CLAUDE.md
24
CLAUDE.md
@@ -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.
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user