From a7f1c77787e784c2f464f1c2e56aa506b64cba03 Mon Sep 17 00:00:00 2001 From: qixinbo Date: Sun, 15 Mar 2026 20:48:40 +0800 Subject: [PATCH] add data source --- .DS_Store | Bin 0 -> 6148 bytes backend/app/agent/nl2sql.py | 14 ++-- backend/app/api/upload.py | 26 +++++-- backend/app/connectors/factory.py | 7 +- backend/app/core/files.py | 18 +++++ backend/data_sources.db | 0 backend/dataclaw.db | Bin 28672 -> 28672 bytes examples/Car_Database.db | Bin 0 -> 65536 bytes examples/file_example_XLS_1000.xls | Bin 0 -> 140288 bytes frontend/src/components/DataSourceForm.tsx | 83 ++++++++++++++++++--- 10 files changed, 119 insertions(+), 29 deletions(-) create mode 100644 .DS_Store create mode 100644 backend/app/core/files.py create mode 100644 backend/data_sources.db create mode 100644 examples/Car_Database.db create mode 100644 examples/file_example_XLS_1000.xls diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..88d8c5c3d35b10fb5857c30fce16cbe9315c7050 GIT binary patch literal 6148 zcmeHK%}T>S5Z-O8-BN@cEO-ofEto<@!Apqs1&ruHr6we3FwK@GwL&T6rZ42H_&m<+ zZotwUJc-yD*nG3|v%B*__J=XXy?HcX%wmiQXowt@8bNcdt6_o>xf)~2QWmp3T;$W5 zf&QWi*DhequPk5__HF%PDnsyngwr(13&;87jb>|Sw=LSDEAIU#x$ujinCD(FyT#s> zlxbM$L3o|Si?OwTF0&#?vUsKvk|>6dyPG77WJRq(b?trIh|zkO*6@XbR~NRD|iQGqo@~umZdU#1Yea`#S;<(!~iis z3~Utx<`@v&t*V-eB?gFrpD=*?gAEPQHCSp?TL*mb`i%Y-A`0mEmO!)(x&}*)-~r(} z6;P*g^Tgmf9qhJ=a}Aanbvol}W*EoJTs>a6njP%63TNEaNMB-r7+7bZsk;`Q{}=Gf z)IRdpQ>aG_5Ci{=0p1$ Path: - if not file_url or not file_url.startswith("local://"): - raise ValueError("Invalid uploaded file URL") - raw_name = file_url.replace("local://", "", 1) - safe_name = os.path.basename(raw_name) - upload_dir = Path(__file__).resolve().parents[2] / "data" / "uploads" - file_path = upload_dir / safe_name - if not file_path.exists(): - raise ValueError(f"Uploaded file not found: {safe_name}") - return file_path + try: + return resolve_upload_file_path(file_url) + except ValueError as e: + raise ValueError(f"Invalid uploaded file URL: {e}") def _load_upload_dataframe_from_path(file_path: Path) -> pd.DataFrame: suffix = file_path.suffix.lower() diff --git a/backend/app/api/upload.py b/backend/app/api/upload.py index 0b53cb5..1f4a1ed 100644 --- a/backend/app/api/upload.py +++ b/backend/app/api/upload.py @@ -11,9 +11,10 @@ upload_dir.mkdir(parents=True, exist_ok=True) @router.post("/upload/file") async def upload_file(file: UploadFile = File(...)): - allowed_extensions = ('.csv', '.xls', '.xlsx') - if not file.filename.lower().endswith(allowed_extensions): - raise HTTPException(status_code=400, detail="Invalid file type. Only CSV and Excel files allowed.") + allowed_extensions = ('.csv', '.xls', '.xlsx', '.parquet', '.db', '.sqlite', '.sqlite3') + filename_lower = file.filename.lower() + if not filename_lower.endswith(allowed_extensions): + raise HTTPException(status_code=400, detail="Invalid file type. Allowed: CSV, Excel, Parquet, SQLite.") try: content = await file.read() @@ -29,11 +30,24 @@ async def upload_file(file: UploadFile = File(...)): file_obj.seek(0) try: - if file.filename.lower().endswith('.csv'): + if filename_lower.endswith('.csv'): df = pd.read_csv(file_obj) - else: + elif filename_lower.endswith(('.xls', '.xlsx')): df = pd.read_excel(file_obj) - + elif filename_lower.endswith('.parquet'): + df = pd.read_parquet(file_obj) + elif filename_lower.endswith(('.db', '.sqlite', '.sqlite3')): + # For SQLite, we don't load into DF immediately for analysis here + # Just return success + return { + "filename": unique_filename, + "url": file_url, + "rows": 0, + "columns": [], + "summary": "SQLite database uploaded" + } + + # For DF supported types duckdb_conn = duckdb.connect(database=':memory:') duckdb_conn.register('uploaded_file', df) summary = duckdb_conn.execute("DESCRIBE uploaded_file").fetchall() diff --git a/backend/app/connectors/factory.py b/backend/app/connectors/factory.py index e96c08d..1023a6c 100644 --- a/backend/app/connectors/factory.py +++ b/backend/app/connectors/factory.py @@ -5,6 +5,7 @@ from app.connectors.postgres import PostgresConnector from app.connectors.clickhouse import ClickHouseConnector from app.connectors.parquet import ParquetConnector from app.models.datasource import DataSource +from app.core.files import resolve_upload_file_path @functools.lru_cache(maxsize=32) def _get_cached_connector(ds_type: str, config_json: str): @@ -20,7 +21,8 @@ def _get_cached_connector(ds_type: str, config_json: str): # SQLite uses connection string usually file path db_url = config.get("connection_string") if not db_url and config.get("file_path"): - db_url = f"sqlite:///{config.get('file_path')}" + file_path = str(resolve_upload_file_path(config.get("file_path"))) + db_url = f"sqlite:///{file_path}" return PostgresConnector(db_url=db_url) elif ds_type == "clickhouse": @@ -33,7 +35,8 @@ def _get_cached_connector(ds_type: str, config_json: str): ) elif ds_type == "parquet": - return ParquetConnector(file_path=config.get("file_path")) + file_path = str(resolve_upload_file_path(config.get("file_path"))) + return ParquetConnector(file_path=file_path) else: raise ValueError(f"Unsupported data source type: {ds_type}") diff --git a/backend/app/core/files.py b/backend/app/core/files.py new file mode 100644 index 0000000..9cffdaa --- /dev/null +++ b/backend/app/core/files.py @@ -0,0 +1,18 @@ +import os +from pathlib import Path +from typing import Optional + +def resolve_upload_file_path(file_url: Optional[str]) -> Path: + if not file_url: + raise ValueError("File URL is empty") + + if file_url.startswith("local://"): + raw_name = file_url.replace("local://", "", 1) + safe_name = os.path.basename(raw_name) + # Assuming we are in backend/app/core, go up to backend/data/uploads + upload_dir = Path(__file__).resolve().parents[2] / "data" / "uploads" + file_path = upload_dir / safe_name + return file_path + + # If it's already an absolute path (or relative path not starting with local://) + return Path(file_url) diff --git a/backend/data_sources.db b/backend/data_sources.db new file mode 100644 index 0000000..e69de29 diff --git a/backend/dataclaw.db b/backend/dataclaw.db index eb104a26c25b96678021848945c92874b4997e2d..2af59774e1b759fc8458f21d2eab2d97c5bcf8be 100644 GIT binary patch delta 191 zcmZp8z}WDBae_1>-$WT_M!t;+OZ?dw`70UtD>n-&E2U-Tq{bH{mSiYdDJbRSCnx4u>FXOBS{Pec7$oW%Bpapbniv`x>snZv8tNJX zNdps;l+?r|16}9DqIj3YlEkFM;#9qqB&Av-10yqC17lr7Qw2jqD-%mA12ZHcA($8! Z0|Nsi{~reaKbr*u-t)5qxy;O*oB#l}F(d#0 delta 45 scmZp8z}WDBae_1>??f4AM&6AHOZ=G_1U3sMyyl-6z|Y3T00eL@06roN%m4rY diff --git a/examples/Car_Database.db b/examples/Car_Database.db new file mode 100644 index 0000000000000000000000000000000000000000..e7334bc813dfaf940b0b0803326f4a536d7ce988 GIT binary patch literal 65536 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 L$Z4PQf8zfy!)|WW literal 0 HcmV?d00001 diff --git a/frontend/src/components/DataSourceForm.tsx b/frontend/src/components/DataSourceForm.tsx index 77af161..ea121a9 100644 --- a/frontend/src/components/DataSourceForm.tsx +++ b/frontend/src/components/DataSourceForm.tsx @@ -1,7 +1,8 @@ -import { useState } from "react"; +import { useState, useRef } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; -import { Loader2, Check, AlertTriangle } from "lucide-react"; +import { Loader2, Check, AlertTriangle, Upload } from "lucide-react"; +import { api } from "@/lib/api"; export interface DataSourceConfig { id?: number; @@ -24,11 +25,43 @@ export function DataSourceForm({ initialData, onSubmit, onTest, onCancel }: Data const [isTesting, setIsTesting] = useState(false); const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null); const [isSaving, setIsSaving] = useState(false); + const [isUploading, setIsUploading] = useState(false); + const fileInputRef = useRef(null); const handleConfigChange = (key: string, value: any) => { setConfig(prev => ({ ...prev, [key]: value })); }; + const handleFileSelect = () => { + fileInputRef.current?.click(); + }; + + const handleFileUpload = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + + setIsUploading(true); + const formData = new FormData(); + formData.append("file", file); + + try { + // @ts-ignore + const res = await api.post("/api/v1/upload/file", formData); + if (res && (res as any).url) { + handleConfigChange("file_path", (res as any).url); + } + } catch (error) { + console.error("Upload failed", error); + alert("上传失败"); + } finally { + setIsUploading(false); + // Clear input value so same file can be selected again + if (fileInputRef.current) { + fileInputRef.current.value = ""; + } + } + }; + const handleTest = async () => { setIsTesting(true); setTestResult(null); @@ -175,11 +208,24 @@ export function DataSourceForm({ initialData, onSubmit, onTest, onCancel }: Data
- handleConfigChange("file_path", e.target.value)} - placeholder="/path/to/database.db" - /> +
+ handleConfigChange("file_path", e.target.value)} + placeholder="/path/to/database.db" + /> + + +
); @@ -188,11 +234,24 @@ export function DataSourceForm({ initialData, onSubmit, onTest, onCancel }: Data
- handleConfigChange("file_path", e.target.value)} - placeholder="/path/to/data.parquet" - /> +
+ handleConfigChange("file_path", e.target.value)} + placeholder="/path/to/data.parquet" + /> + + +
);