From 2f9fdb9a8afa176563f7c909dea56b08c2c12682 Mon Sep 17 00:00:00 2001 From: Ravindrayadav04 Date: Tue, 31 Mar 2026 15:10:17 +0530 Subject: [PATCH 01/12] inital lancedb setup at local --- backend/constants.py | 4 +- backend/image_search/db/create_table.py | 3 + backend/main.py | 3 +- frontend/package-lock.json | 893 +++++++++++++++++++++++- 4 files changed, 894 insertions(+), 9 deletions(-) diff --git a/backend/constants.py b/backend/constants.py index e08c13b..d59fa8d 100644 --- a/backend/constants.py +++ b/backend/constants.py @@ -7,7 +7,7 @@ load_env() TABLE_NAME = "tz-fabric-table" PROJECT_DIR = Path(__file__).parent -RELATIVE_GENERATED_FOLDER = "s3://threadzip-bucket/images/" +RELATIVE_GENERATED_FOLDER = "s3://tz-fabric-upload/uploaded/" ASSETS = PROJECT_DIR / "assets" UPLOAD_FOLDER_FABRIC = ASSETS / "search" IMAGE_DIR = ASSETS / "images" @@ -28,7 +28,7 @@ "api_url": "https://api.github.com/repos/recursivezero/tz-script/releases/tags/v3.5.0", }, } -DATABASE_PATH = str(PROJECT_DIR / "database") +DATABASE_PATH = str(PROJECT_DIR / "D:\\project\\tz-fabric\\backend\\lancedb") FABRIC_COLLECTION = "fabric_data" PROCESSING_TIMES_COLLECTION = "fabric_log" diff --git a/backend/image_search/db/create_table.py b/backend/image_search/db/create_table.py index b073f86..9141464 100644 --- a/backend/image_search/db/create_table.py +++ b/backend/image_search/db/create_table.py @@ -79,6 +79,7 @@ def collect_image_data(root_folder: str) -> list: if root_folder.startswith("s3://"): parsed = urlparse(root_folder) + print(f"Parsed S3 URI: {parsed}") bucket_name = parsed.netloc base_prefix = parsed.path.lstrip("/") @@ -279,6 +280,8 @@ def process_table( logger.info(TABLE_MESSAGES.info.connecting) db = lancedb.connect(database) + if not hasattr(db, "list_tables"): + db.list_tables = db.table_names logger.info(TABLE_MESSAGES.info.available_tables.format(tables=db.list_tables())) logger.info(TABLE_MESSAGES.info.looking_for_table.format(table_name=table_name)) logger.info( diff --git a/backend/main.py b/backend/main.py index 1c21faa..58f0bdf 100644 --- a/backend/main.py +++ b/backend/main.py @@ -47,7 +47,8 @@ class MyApp(FastAPI): origins = [ "http://localhost:4173", - "http://localhost:5173", # Allow requests from your frontend origin + "http://localhost:5173", + "http://localhost:5174" # Allow requests from your frontend origin # You can add more origins here if needed ] diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4cfa33b..44e1280 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -318,23 +318,567 @@ "@biomejs/cli-win32-x64": "2.3.10" } }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.10.tgz", + "integrity": "sha512-M6xUjtCVnNGFfK7HMNKa593nb7fwNm43fq1Mt71kpLpb+4mE7odO8W/oWVDyBVO4ackhresy1ZYO7OJcVo/B7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.10.tgz", + "integrity": "sha512-Vae7+V6t/Avr8tVbFNjnFSTKZogZHFYl7MMH62P/J1kZtr0tyRQ9Fe0onjqjS2Ek9lmNLmZc/VR5uSekh+p1fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.10.tgz", + "integrity": "sha512-hhPw2V3/EpHKsileVOFynuWiKRgFEV48cLe0eA+G2wO4SzlwEhLEB9LhlSrVeu2mtSn205W283LkX7Fh48CaxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.10.tgz", + "integrity": "sha512-B9DszIHkuKtOH2IFeeVkQmSMVUjss9KtHaNXquYYWCjH8IstNgXgx5B0aSBQNr6mn4RcKKRQZXn9Zu1rM3O0/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, "node_modules/@biomejs/cli-linux-x64": { "version": "2.3.10", "cpu": [ - "x64" + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.10.tgz", + "integrity": "sha512-QTfHZQh62SDFdYc2nfmZFuTm5yYb4eO1zwfB+90YxUumRCR171tS1GoTX5OD0wrv4UsziMPmrePMtkTnNyYG3g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.10.tgz", + "integrity": "sha512-o7lYc9n+CfRbHvkjPhm8s9FgbKdYZu5HCcGVMItLjz93EhgJ8AM44W+QckDqLA9MKDNFrR8nPbO4b73VC5kGGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.10.tgz", + "integrity": "sha512-pHEFgq7dUEsKnqG9mx9bXihxGI49X+ar+UBrEIj3Wqj3UCZp1rNgV+OoyjFgcXsjCWpuEAF4VJdkZr3TrWdCbQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" ], "dev": true, - "license": "MIT OR Apache-2.0", + "license": "MIT", "optional": true, "os": [ - "linux" + "win32" ], "engines": { - "node": ">=14.21.3" + "node": ">=18" } }, - "node_modules/@esbuild/linux-x64": { + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", "cpu": [ "x64" ], @@ -342,7 +886,7 @@ "license": "MIT", "optional": true, "os": [ - "linux" + "win32" ], "engines": { "node": ">=18" @@ -572,6 +1116,244 @@ "dev": true, "license": "MIT" }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.59.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", @@ -600,6 +1382,90 @@ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@types/babel__core": { "version": "7.20.5", "dev": true, @@ -1820,6 +2686,21 @@ "node": ">= 6" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "license": "MIT", From 58edcaee7feb80370f5f7339b4a232489cc5a49e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 17:09:48 +0000 Subject: [PATCH 02/12] chore: update backend requirements.txt [skip ci] --- backend/requirements.txt | 77 ++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index 2e306de..d3de41d 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,16 +1,16 @@ annotated-doc==0.0.4 ; python_version >= "3.11" and python_version < "3.13" annotated-types==0.7.0 ; python_version >= "3.11" and python_version < "3.13" -anyio==4.12.1 ; python_version >= "3.11" and python_version < "3.13" +anyio==4.13.0 ; python_version >= "3.11" and python_version < "3.13" async-timeout==5.0.1 ; python_version >= "3.11" and python_full_version < "3.11.3" -attrs==25.4.0 ; python_version >= "3.11" and python_version < "3.13" +attrs==26.1.0 ; python_version >= "3.11" and python_version < "3.13" authlib==1.6.9 ; python_version >= "3.11" and python_version < "3.13" backports-tarfile==1.2.0 ; python_version == "3.11" bcrypt==5.0.0 ; python_version >= "3.11" and python_version < "3.13" beartype==0.22.9 ; python_version >= "3.11" and python_version < "3.13" -better-profanity==0.7.0 -boto3==1.42.70 ; python_version >= "3.11" and python_version < "3.13" -botocore==1.42.70 ; python_version >= "3.11" and python_version < "3.13" -build==1.4.0 ; python_version >= "3.11" and python_version < "3.13" +better-profanity==0.7.0 ; python_version >= "3.11" and python_version < "3.13" +boto3==1.42.77 ; python_version >= "3.11" and python_version < "3.13" +botocore==1.42.77 ; python_version >= "3.11" and python_version < "3.13" +build==1.4.2 ; python_version >= "3.11" and python_version < "3.13" cachetools==7.0.5 ; python_version >= "3.11" and python_version < "3.13" certifi==2026.2.25 ; python_version >= "3.11" and python_version < "3.13" cffi==2.0.0 ; python_version >= "3.11" and python_version < "3.13" and platform_python_implementation != "PyPy" @@ -20,10 +20,11 @@ click==8.3.1 ; python_version >= "3.11" and python_version < "3.13" cloudpickle==3.1.2 ; python_version >= "3.11" and python_version < "3.13" colorama==0.4.6 ; python_version >= "3.11" and python_version < "3.13" and (os_name == "nt" or platform_system == "Windows" or sys_platform == "win32") cronsim==2.7 ; python_version >= "3.11" and python_version < "3.13" -cryptography==46.0.5 ; python_version >= "3.11" and python_version < "3.13" -cuda-bindings==12.9.4 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and platform_machine == "x86_64" -cuda-pathfinder==1.4.3 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and platform_machine == "x86_64" -cyclopts==4.10.0 ; python_version >= "3.11" and python_version < "3.13" +cryptography==46.0.6 ; python_version >= "3.11" and python_version < "3.13" +cuda-bindings==13.2.0 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" +cuda-pathfinder==1.5.0 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" +cuda-toolkit==13.0.2 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" +cyclopts==4.10.1 ; python_version >= "3.11" and python_version < "3.13" deprecation==2.1.0 ; python_version >= "3.11" and python_version < "3.13" diskcache==5.6.3 ; python_version >= "3.11" and python_version < "3.13" distro==1.9.0 ; python_version >= "3.11" and python_version < "3.13" @@ -41,7 +42,7 @@ fastmcp==2.14.5 ; python_version >= "3.11" and python_version < "3.13" filelock==3.25.2 ; python_version >= "3.11" and python_version < "3.13" flatbuffers==25.12.19 ; python_version >= "3.11" and python_version < "3.13" fsspec==2026.2.0 ; python_version >= "3.11" and python_version < "3.13" -googleapis-common-protos==1.73.0 ; python_version >= "3.11" and python_version < "3.13" +googleapis-common-protos==1.73.1 ; python_version >= "3.11" and python_version < "3.13" groq==0.37.1 ; python_version >= "3.11" and python_version < "3.13" grpcio==1.78.0 ; python_version >= "3.11" and python_version < "3.13" h11==0.16.0 ; python_version >= "3.11" and python_version < "3.13" @@ -56,13 +57,13 @@ imageio==2.37.3 ; python_version >= "3.11" and python_version < "3.13" importlib-metadata==8.7.1 ; python_version >= "3.11" and python_version < "3.13" importlib-resources==6.5.2 ; python_version >= "3.11" and python_version < "3.13" jaraco-classes==3.4.0 ; python_version >= "3.11" and python_version < "3.13" -jaraco-context==6.1.1 ; python_version >= "3.11" and python_version < "3.13" +jaraco-context==6.1.2 ; python_version >= "3.11" and python_version < "3.13" jaraco-functools==4.4.0 ; python_version >= "3.11" and python_version < "3.13" jeepney==0.9.0 ; python_version >= "3.11" and python_version < "3.13" and sys_platform == "linux" jinja2==3.1.6 ; python_version >= "3.11" and python_version < "3.13" jmespath==1.1.0 ; python_version >= "3.11" and python_version < "3.13" jsonpatch==1.33 ; python_version >= "3.11" and python_version < "3.13" -jsonpointer==3.0.0 ; python_version >= "3.11" and python_version < "3.13" +jsonpointer==3.1.1 ; python_version >= "3.11" and python_version < "3.13" jsonref==1.1.0 ; python_version >= "3.11" and python_version < "3.13" jsonschema-path==0.4.5 ; python_version >= "3.11" and python_version < "3.13" jsonschema-specifications==2025.9.1 ; python_version >= "3.11" and python_version < "3.13" @@ -71,10 +72,10 @@ keyring==25.7.0 ; python_version >= "3.11" and python_version < "3.13" kubernetes==35.0.0 ; python_version >= "3.11" and python_version < "3.13" lance-namespace-urllib3-client==0.6.1 ; python_version >= "3.11" and python_version < "3.13" lance-namespace==0.6.1 ; python_version >= "3.11" and python_version < "3.13" -lancedb==0.30.0 ; python_version >= "3.11" and python_version < "3.13" -langchain-core==1.2.19 ; python_version >= "3.11" and python_version < "3.13" +lancedb==0.30.1 ; python_version >= "3.11" and python_version < "3.13" +langchain-core==1.2.22 ; python_version >= "3.11" and python_version < "3.13" langchain-groq==1.1.2 ; python_version >= "3.11" and python_version < "3.13" -langsmith==0.7.20 ; python_version >= "3.11" and python_version < "3.13" +langsmith==0.7.22 ; python_version >= "3.11" and python_version < "3.13" lazy-loader==0.5 ; python_version >= "3.11" and python_version < "3.13" lupa==2.6 ; python_version >= "3.11" and python_version < "3.13" markdown-it-py==4.0.0 ; python_version >= "3.11" and python_version < "3.13" @@ -87,21 +88,21 @@ mpmath==1.3.0 ; python_version >= "3.11" and python_version < "3.13" networkx==3.6.1 ; python_version >= "3.11" and python_version < "3.13" ninja==1.13.0 ; python_version >= "3.11" and python_version < "3.13" numpy==2.2.6 ; python_version >= "3.11" and python_version < "3.13" -nvidia-cublas-cu12==12.8.4.1 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and platform_machine == "x86_64" -nvidia-cuda-cupti-cu12==12.8.90 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and platform_machine == "x86_64" -nvidia-cuda-nvrtc-cu12==12.8.93 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and platform_machine == "x86_64" -nvidia-cuda-runtime-cu12==12.8.90 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and platform_machine == "x86_64" -nvidia-cudnn-cu12==9.10.2.21 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and platform_machine == "x86_64" -nvidia-cufft-cu12==11.3.3.83 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and platform_machine == "x86_64" -nvidia-cufile-cu12==1.13.1.3 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and platform_machine == "x86_64" -nvidia-curand-cu12==10.3.9.90 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and platform_machine == "x86_64" -nvidia-cusolver-cu12==11.7.3.90 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and platform_machine == "x86_64" -nvidia-cusparse-cu12==12.5.8.93 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and platform_machine == "x86_64" -nvidia-cusparselt-cu12==0.7.1 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and platform_machine == "x86_64" -nvidia-nccl-cu12==2.27.5 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and platform_machine == "x86_64" -nvidia-nvjitlink-cu12==12.8.93 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and platform_machine == "x86_64" -nvidia-nvshmem-cu12==3.4.5 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and platform_machine == "x86_64" -nvidia-nvtx-cu12==12.8.90 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and platform_machine == "x86_64" +nvidia-cublas==13.1.0.3 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" +nvidia-cuda-cupti==13.0.85 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and (sys_platform == "linux" or sys_platform == "win32") +nvidia-cuda-nvrtc==13.0.88 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and (sys_platform == "linux" or sys_platform == "win32") +nvidia-cuda-runtime==13.0.96 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and (sys_platform == "linux" or sys_platform == "win32") +nvidia-cudnn-cu13==9.19.0.56 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" +nvidia-cufft==12.0.0.61 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and (sys_platform == "linux" or sys_platform == "win32") +nvidia-cufile==1.15.1.6 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and sys_platform == "linux" +nvidia-curand==10.4.0.35 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and (sys_platform == "linux" or sys_platform == "win32") +nvidia-cusolver==12.0.4.66 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and (sys_platform == "linux" or sys_platform == "win32") +nvidia-cusparse==12.6.3.3 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and (sys_platform == "linux" or sys_platform == "win32") +nvidia-cusparselt-cu13==0.8.0 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" +nvidia-nccl-cu13==2.28.9 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" +nvidia-nvjitlink==13.0.88 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and (sys_platform == "linux" or sys_platform == "win32") +nvidia-nvshmem-cu13==3.4.5 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" +nvidia-nvtx==13.0.85 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and (sys_platform == "linux" or sys_platform == "win32") oauthlib==3.3.1 ; python_version >= "3.11" and python_version < "3.13" onnxruntime==1.24.4 ; python_version >= "3.11" and python_version < "3.13" openapi-pydantic==0.5.1 ; python_version >= "3.11" and python_version < "3.13" @@ -123,7 +124,7 @@ pillow==12.1.1 ; python_version >= "3.11" and python_version < "3.13" platformdirs==4.9.4 ; python_version >= "3.11" and python_version < "3.13" profanity-hinglish==0.1.5 ; python_version >= "3.11" and python_version < "3.13" prometheus-client==0.24.1 ; python_version >= "3.11" and python_version < "3.13" -protobuf==6.33.5 ; python_version >= "3.11" and python_version < "3.13" +protobuf==6.33.6 ; python_version >= "3.11" and python_version < "3.13" py-key-value-aio==0.3.0 ; python_version >= "3.11" and python_version < "3.13" py-key-value-shared==0.3.0 ; python_version >= "3.11" and python_version < "3.13" pyarrow==23.0.1 ; python_version >= "3.11" and python_version < "3.13" @@ -149,7 +150,7 @@ python-multipart==0.0.22 ; python_version >= "3.11" and python_version < "3.13" pywin32-ctypes==0.2.3 ; python_version >= "3.11" and python_version < "3.13" and sys_platform == "win32" pywin32==311 ; python_version >= "3.11" and python_version < "3.13" and sys_platform == "win32" pyyaml==6.0.3 ; python_version >= "3.11" and python_version < "3.13" -redis==7.3.0 ; python_version >= "3.11" and python_version < "3.13" +redis==7.4.0 ; python_version >= "3.11" and python_version < "3.13" referencing==0.37.0 ; python_version >= "3.11" and python_version < "3.13" requests-oauthlib==2.0.0 ; python_version >= "3.11" and python_version < "3.13" requests-toolbelt==1.0.0 ; python_version >= "3.11" and python_version < "3.13" @@ -161,7 +162,7 @@ s3transfer==0.16.0 ; python_version >= "3.11" and python_version < "3.13" scikit-image==0.26.0 ; python_version >= "3.11" and python_version < "3.13" scipy==1.17.1 ; python_version >= "3.11" and python_version < "3.13" secretstorage==3.5.0 ; python_version >= "3.11" and python_version < "3.13" and sys_platform == "linux" -setuptools==82.0.1 ; python_version == "3.12" +setuptools==81.0.0 ; python_version >= "3.11" and python_version < "3.13" shapely==2.1.2 ; python_version >= "3.11" and python_version < "3.13" shellingham==1.5.4 ; python_version >= "3.11" and python_version < "3.13" six==1.17.0 ; python_version >= "3.11" and python_version < "3.13" @@ -173,10 +174,10 @@ sympy==1.14.0 ; python_version >= "3.11" and python_version < "3.13" tenacity==9.1.4 ; python_version >= "3.11" and python_version < "3.13" tifffile==2026.3.3 ; python_version >= "3.11" and python_version < "3.13" tokenizers==0.22.2 ; python_version >= "3.11" and python_version < "3.13" -torch==2.10.0 ; python_version >= "3.11" and python_version < "3.13" -torchvision==0.25.0 ; python_version >= "3.11" and python_version < "3.13" +torch==2.11.0 ; python_version >= "3.11" and python_version < "3.13" +torchvision==0.26.0 ; python_version >= "3.11" and python_version < "3.13" tqdm==4.67.3 ; python_version >= "3.11" and python_version < "3.13" -triton==3.6.0 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and platform_machine == "x86_64" +triton==3.6.0 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" typer==0.24.1 ; python_version >= "3.11" and python_version < "3.13" typing-extensions==4.15.0 ; python_version >= "3.11" and python_version < "3.13" typing-inspection==0.4.2 ; python_version >= "3.11" and python_version < "3.13" @@ -189,7 +190,7 @@ uvloop==0.22.1 ; python_version >= "3.11" and python_version < "3.13" and sys_pl watchfiles==1.1.1 ; python_version >= "3.11" and python_version < "3.13" websocket-client==1.9.0 ; python_version >= "3.11" and python_version < "3.13" websockets==16.0 ; python_version >= "3.11" and python_version < "3.13" -werkzeug==3.1.6 ; python_version >= "3.11" and python_version < "3.13" +werkzeug==3.1.7 ; python_version >= "3.11" and python_version < "3.13" xxhash==3.6.0 ; python_version >= "3.11" and python_version < "3.13" zipp==3.23.0 ; python_version >= "3.11" and python_version < "3.13" zstandard==0.25.0 ; python_version >= "3.11" and python_version < "3.13" From 68094c755bc0a3ae52019ec9cf71d8a63f5ccb67 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 16:38:23 +0000 Subject: [PATCH 03/12] chore: update backend requirements.txt [skip ci] --- backend/requirements.txt | 196 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 backend/requirements.txt diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..89bd8e8 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,196 @@ +annotated-doc==0.0.4 ; python_version >= "3.11" and python_version < "3.13" +annotated-types==0.7.0 ; python_version >= "3.11" and python_version < "3.13" +anyio==4.13.0 ; python_version >= "3.11" and python_version < "3.13" +async-timeout==5.0.1 ; python_version >= "3.11" and python_full_version < "3.11.3" +attrs==26.1.0 ; python_version >= "3.11" and python_version < "3.13" +authlib==1.6.9 ; python_version >= "3.11" and python_version < "3.13" +backports-tarfile==1.2.0 ; python_version == "3.11" +bcrypt==5.0.0 ; python_version >= "3.11" and python_version < "3.13" +beartype==0.22.9 ; python_version >= "3.11" and python_version < "3.13" +better-profanity==0.7.0 ; python_version >= "3.11" and python_version < "3.13" +boto3==1.42.80 ; python_version >= "3.11" and python_version < "3.13" +botocore==1.42.80 ; python_version >= "3.11" and python_version < "3.13" +build==1.4.2 ; python_version >= "3.11" and python_version < "3.13" +cachetools==7.0.5 ; python_version >= "3.11" and python_version < "3.13" +certifi==2026.2.25 ; python_version >= "3.11" and python_version < "3.13" +cffi==2.0.0 ; python_version >= "3.11" and python_version < "3.13" and platform_python_implementation != "PyPy" +charset-normalizer==3.4.6 ; python_version >= "3.11" and python_version < "3.13" +chromadb==1.5.5 ; python_version >= "3.11" and python_version < "3.13" +click==8.3.1 ; python_version >= "3.11" and python_version < "3.13" +cloudpickle==3.1.2 ; python_version >= "3.11" and python_version < "3.13" +colorama==0.4.6 ; python_version >= "3.11" and python_version < "3.13" and (os_name == "nt" or platform_system == "Windows" or sys_platform == "win32") +cronsim==2.7 ; python_version >= "3.11" and python_version < "3.13" +cryptography==46.0.6 ; python_version >= "3.11" and python_version < "3.13" +cuda-bindings==13.2.0 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" +cuda-pathfinder==1.5.0 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" +cuda-toolkit==13.0.2 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" +cyclopts==4.10.1 ; python_version >= "3.11" and python_version < "3.13" +deprecation==2.1.0 ; python_version >= "3.11" and python_version < "3.13" +diskcache==5.6.3 ; python_version >= "3.11" and python_version < "3.13" +distro==1.9.0 ; python_version >= "3.11" and python_version < "3.13" +dnspython==2.8.0 ; python_version >= "3.11" and python_version < "3.13" +docstring-parser==0.17.0 ; python_version >= "3.11" and python_version < "3.13" +docutils==0.22.4 ; python_version >= "3.11" and python_version < "3.13" +dotenv==0.9.9 ; python_version >= "3.11" and python_version < "3.13" +durationpy==0.10 ; python_version >= "3.11" and python_version < "3.13" +easyocr==1.7.2 ; python_version >= "3.11" and python_version < "3.13" +email-validator==2.3.0 ; python_version >= "3.11" and python_version < "3.13" +exceptiongroup==1.3.1 ; python_version >= "3.11" and python_version < "3.13" +fakeredis==2.34.1 ; python_version >= "3.11" and python_version < "3.13" +fastapi==0.116.2 ; python_version >= "3.11" and python_version < "3.13" +fastmcp==2.14.6 ; python_version >= "3.11" and python_version < "3.13" +filelock==3.25.2 ; python_version >= "3.11" and python_version < "3.13" +flatbuffers==25.12.19 ; python_version >= "3.11" and python_version < "3.13" +fsspec==2026.3.0 ; python_version >= "3.11" and python_version < "3.13" +googleapis-common-protos==1.73.1 ; python_version >= "3.11" and python_version < "3.13" +groq==0.37.1 ; python_version >= "3.11" and python_version < "3.13" +grpcio==1.80.0 ; python_version >= "3.11" and python_version < "3.13" +h11==0.16.0 ; python_version >= "3.11" and python_version < "3.13" +hf-xet==1.4.3 ; python_version >= "3.11" and python_version < "3.13" and (platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "arm64" or platform_machine == "aarch64") +httpcore==1.0.9 ; python_version >= "3.11" and python_version < "3.13" +httptools==0.7.1 ; python_version >= "3.11" and python_version < "3.13" +httpx-sse==0.4.3 ; python_version >= "3.11" and python_version < "3.13" +httpx==0.28.1 ; python_version >= "3.11" and python_version < "3.13" +huggingface-hub==0.36.2 ; python_version >= "3.11" and python_version < "3.13" +idna==3.11 ; python_version >= "3.11" and python_version < "3.13" +imageio==2.37.3 ; python_version >= "3.11" and python_version < "3.13" +importlib-metadata==8.7.1 ; python_version >= "3.11" and python_version < "3.13" +importlib-resources==6.5.2 ; python_version >= "3.11" and python_version < "3.13" +jaraco-classes==3.4.0 ; python_version >= "3.11" and python_version < "3.13" +jaraco-context==6.1.2 ; python_version >= "3.11" and python_version < "3.13" +jaraco-functools==4.4.0 ; python_version >= "3.11" and python_version < "3.13" +jeepney==0.9.0 ; python_version >= "3.11" and python_version < "3.13" and sys_platform == "linux" +jinja2==3.1.6 ; python_version >= "3.11" and python_version < "3.13" +jmespath==1.1.0 ; python_version >= "3.11" and python_version < "3.13" +jsonpatch==1.33 ; python_version >= "3.11" and python_version < "3.13" +jsonpointer==3.1.1 ; python_version >= "3.11" and python_version < "3.13" +jsonref==1.1.0 ; python_version >= "3.11" and python_version < "3.13" +jsonschema-path==0.4.5 ; python_version >= "3.11" and python_version < "3.13" +jsonschema-specifications==2025.9.1 ; python_version >= "3.11" and python_version < "3.13" +jsonschema==4.26.0 ; python_version >= "3.11" and python_version < "3.13" +keyring==25.7.0 ; python_version >= "3.11" and python_version < "3.13" +kubernetes==35.0.0 ; python_version >= "3.11" and python_version < "3.13" +lance-namespace-urllib3-client==0.6.1 ; python_version >= "3.11" and python_version < "3.13" +lance-namespace==0.6.1 ; python_version >= "3.11" and python_version < "3.13" +lancedb==0.30.2 ; python_version >= "3.11" and python_version < "3.13" +langchain-core==1.2.23 ; python_version >= "3.11" and python_version < "3.13" +langchain-groq==1.1.2 ; python_version >= "3.11" and python_version < "3.13" +langsmith==0.7.23 ; python_version >= "3.11" and python_version < "3.13" +lazy-loader==0.5 ; python_version >= "3.11" and python_version < "3.13" +lupa==2.6 ; python_version >= "3.11" and python_version < "3.13" +markdown-it-py==4.0.0 ; python_version >= "3.11" and python_version < "3.13" +markupsafe==3.0.3 ; python_version >= "3.11" and python_version < "3.13" +mcp==1.26.0 ; python_version >= "3.11" and python_version < "3.13" +mdurl==0.1.2 ; python_version >= "3.11" and python_version < "3.13" +mmh3==5.2.1 ; python_version >= "3.11" and python_version < "3.13" +more-itertools==10.8.0 ; python_version >= "3.11" and python_version < "3.13" +mpmath==1.3.0 ; python_version >= "3.11" and python_version < "3.13" +networkx==3.6.1 ; python_version >= "3.11" and python_version < "3.13" +ninja==1.13.0 ; python_version >= "3.11" and python_version < "3.13" +numpy==2.2.6 ; python_version >= "3.11" and python_version < "3.13" +nvidia-cublas==13.1.0.3 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" +nvidia-cuda-cupti==13.0.85 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and (sys_platform == "linux" or sys_platform == "win32") +nvidia-cuda-nvrtc==13.0.88 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and (sys_platform == "linux" or sys_platform == "win32") +nvidia-cuda-runtime==13.0.96 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and (sys_platform == "linux" or sys_platform == "win32") +nvidia-cudnn-cu13==9.19.0.56 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" +nvidia-cufft==12.0.0.61 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and (sys_platform == "linux" or sys_platform == "win32") +nvidia-cufile==1.15.1.6 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and sys_platform == "linux" +nvidia-curand==10.4.0.35 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and (sys_platform == "linux" or sys_platform == "win32") +nvidia-cusolver==12.0.4.66 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and (sys_platform == "linux" or sys_platform == "win32") +nvidia-cusparse==12.6.3.3 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and (sys_platform == "linux" or sys_platform == "win32") +nvidia-cusparselt-cu13==0.8.0 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" +nvidia-nccl-cu13==2.28.9 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" +nvidia-nvjitlink==13.0.88 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and (sys_platform == "linux" or sys_platform == "win32") +nvidia-nvshmem-cu13==3.4.5 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" +nvidia-nvtx==13.0.85 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" and (sys_platform == "linux" or sys_platform == "win32") +oauthlib==3.3.1 ; python_version >= "3.11" and python_version < "3.13" +onnxruntime==1.24.4 ; python_version >= "3.11" and python_version < "3.13" +openapi-pydantic==0.5.1 ; python_version >= "3.11" and python_version < "3.13" +opencv-python-headless==4.13.0.92 ; python_version >= "3.11" and python_version < "3.13" +opencv-python==4.13.0.92 ; python_version >= "3.11" and python_version < "3.13" +opentelemetry-api==1.40.0 ; python_version >= "3.11" and python_version < "3.13" +opentelemetry-exporter-otlp-proto-common==1.40.0 ; python_version >= "3.11" and python_version < "3.13" +opentelemetry-exporter-otlp-proto-grpc==1.40.0 ; python_version >= "3.11" and python_version < "3.13" +opentelemetry-proto==1.40.0 ; python_version >= "3.11" and python_version < "3.13" +opentelemetry-sdk==1.40.0 ; python_version >= "3.11" and python_version < "3.13" +opentelemetry-semantic-conventions==0.61b0 ; python_version >= "3.11" and python_version < "3.13" +orjson==3.11.8 ; python_version >= "3.11" and python_version < "3.13" +overrides==7.7.0 ; python_version >= "3.11" and python_version < "3.13" +packaging==26.0 ; python_version >= "3.11" and python_version < "3.13" +pandas==3.0.2 ; python_version >= "3.11" and python_version < "3.13" +pathable==0.5.0 ; python_version >= "3.11" and python_version < "3.13" +pathvalidate==3.3.1 ; python_version >= "3.11" and python_version < "3.13" +pillow==12.2.0 ; python_version >= "3.11" and python_version < "3.13" +platformdirs==4.9.4 ; python_version >= "3.11" and python_version < "3.13" +profanity-hinglish==0.1.5 ; python_version >= "3.11" and python_version < "3.13" +prometheus-client==0.24.1 ; python_version >= "3.11" and python_version < "3.13" +protobuf==6.33.6 ; python_version >= "3.11" and python_version < "3.13" +py-key-value-aio==0.3.0 ; python_version >= "3.11" and python_version < "3.13" +py-key-value-shared==0.3.0 ; python_version >= "3.11" and python_version < "3.13" +pyarrow==23.0.1 ; python_version >= "3.11" and python_version < "3.13" +pybase64==1.4.3 ; python_version >= "3.11" and python_version < "3.13" +pyclipper==1.4.0 ; python_version >= "3.11" and python_version < "3.13" +pycparser==3.0 ; python_version >= "3.11" and python_version < "3.13" and platform_python_implementation != "PyPy" and implementation_name != "PyPy" +pydantic-core==2.41.5 ; python_version >= "3.11" and python_version < "3.13" +pydantic-settings==2.13.1 ; python_version >= "3.11" and python_version < "3.13" +pydantic==2.12.5 ; python_version >= "3.11" and python_version < "3.13" +pydocket==0.18.2 ; python_version >= "3.11" and python_version < "3.13" +pygments==2.20.0 ; python_version >= "3.11" and python_version < "3.13" +pyjwt==2.12.1 ; python_version >= "3.11" and python_version < "3.13" +pymongo==4.16.0 ; python_version >= "3.11" and python_version < "3.13" +pyperclip==1.11.0 ; python_version >= "3.11" and python_version < "3.13" +pypika==0.51.1 ; python_version >= "3.11" and python_version < "3.13" +pyproject-hooks==1.2.0 ; python_version >= "3.11" and python_version < "3.13" +python-bidi==0.6.7 ; python_version >= "3.11" and python_version < "3.13" +python-box==7.4.1 ; python_version >= "3.11" and python_version < "3.13" +python-dateutil==2.9.0.post0 ; python_version >= "3.11" and python_version < "3.13" +python-dotenv==1.2.2 ; python_version >= "3.11" and python_version < "3.13" +python-json-logger==4.1.0 ; python_version >= "3.11" and python_version < "3.13" +python-multipart==0.0.22 ; python_version >= "3.11" and python_version < "3.13" +pywin32-ctypes==0.2.3 ; python_version >= "3.11" and python_version < "3.13" and sys_platform == "win32" +pywin32==311 ; python_version >= "3.11" and python_version < "3.13" and sys_platform == "win32" +pyyaml==6.0.3 ; python_version >= "3.11" and python_version < "3.13" +redis==7.4.0 ; python_version >= "3.11" and python_version < "3.13" +referencing==0.37.0 ; python_version >= "3.11" and python_version < "3.13" +requests-oauthlib==2.0.0 ; python_version >= "3.11" and python_version < "3.13" +requests-toolbelt==1.0.0 ; python_version >= "3.11" and python_version < "3.13" +requests==2.33.1 ; python_version >= "3.11" and python_version < "3.13" +rich-rst==1.3.2 ; python_version >= "3.11" and python_version < "3.13" +rich==14.3.3 ; python_version >= "3.11" and python_version < "3.13" +rpds-py==0.30.0 ; python_version >= "3.11" and python_version < "3.13" +s3transfer==0.16.0 ; python_version >= "3.11" and python_version < "3.13" +scikit-image==0.26.0 ; python_version >= "3.11" and python_version < "3.13" +scipy==1.17.1 ; python_version >= "3.11" and python_version < "3.13" +secretstorage==3.5.0 ; python_version >= "3.11" and python_version < "3.13" and sys_platform == "linux" +setuptools==81.0.0 ; python_version >= "3.11" and python_version < "3.13" +shapely==2.1.2 ; python_version >= "3.11" and python_version < "3.13" +shellingham==1.5.4 ; python_version >= "3.11" and python_version < "3.13" +six==1.17.0 ; python_version >= "3.11" and python_version < "3.13" +sniffio==1.3.1 ; python_version >= "3.11" and python_version < "3.13" +sortedcontainers==2.4.0 ; python_version >= "3.11" and python_version < "3.13" +sse-starlette==3.0.3 ; python_version >= "3.11" and python_version < "3.13" +starlette==0.48.0 ; python_version >= "3.11" and python_version < "3.13" +sympy==1.14.0 ; python_version >= "3.11" and python_version < "3.13" +tenacity==9.1.4 ; python_version >= "3.11" and python_version < "3.13" +tifffile==2026.3.3 ; python_version >= "3.11" and python_version < "3.13" +tokenizers==0.22.2 ; python_version >= "3.11" and python_version < "3.13" +torch==2.11.0 ; python_version >= "3.11" and python_version < "3.13" +torchvision==0.26.0 ; python_version >= "3.11" and python_version < "3.13" +tqdm==4.67.3 ; python_version >= "3.11" and python_version < "3.13" +triton==3.6.0 ; python_version >= "3.11" and python_version < "3.13" and platform_system == "Linux" +typer==0.24.1 ; python_version >= "3.11" and python_version < "3.13" +typing-extensions==4.15.0 ; python_version >= "3.11" and python_version < "3.13" +typing-inspection==0.4.2 ; python_version >= "3.11" and python_version < "3.13" +tzdata==2025.3 ; python_version >= "3.11" and python_version < "3.13" and (sys_platform == "win32" or sys_platform == "emscripten") +uncalled-for==0.2.0 ; python_version >= "3.11" and python_version < "3.13" +urllib3==2.6.3 ; python_version >= "3.11" and python_version < "3.13" +uuid-utils==0.14.1 ; python_version >= "3.11" and python_version < "3.13" +uvicorn==0.40.0 ; python_version >= "3.11" and python_version < "3.13" +uvloop==0.22.1 ; python_version >= "3.11" and python_version < "3.13" and sys_platform != "win32" and sys_platform != "cygwin" and platform_python_implementation != "PyPy" +watchfiles==1.1.1 ; python_version >= "3.11" and python_version < "3.13" +websocket-client==1.9.0 ; python_version >= "3.11" and python_version < "3.13" +websockets==16.0 ; python_version >= "3.11" and python_version < "3.13" +werkzeug==3.1.7 ; python_version >= "3.11" and python_version < "3.13" +xxhash==3.6.0 ; python_version >= "3.11" and python_version < "3.13" +zipp==3.23.0 ; python_version >= "3.11" and python_version < "3.13" +zstandard==0.25.0 ; python_version >= "3.11" and python_version < "3.13" From ef6adabdc2f20d296b9014fd88afc20596b2cac5 Mon Sep 17 00:00:00 2001 From: Ravindrayadav04 Date: Wed, 1 Apr 2026 23:08:18 +0530 Subject: [PATCH 04/12] byte seek issue fixed --- backend/routes/search.py | 2 +- backend/utils/aws_helper.py | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/backend/routes/search.py b/backend/routes/search.py index 7f843cb..b95f2e1 100644 --- a/backend/routes/search.py +++ b/backend/routes/search.py @@ -154,7 +154,7 @@ async def image_search( elif search_term: search_start = time.time() limit = limit or 20 - _, all_results = run_vector_search( + _, all_results = await run_vector_search( table, Fabric, search_term, limit=limit, category=parsed_categories ) diff --git a/backend/utils/aws_helper.py b/backend/utils/aws_helper.py index 5501e72..b55f3a3 100644 --- a/backend/utils/aws_helper.py +++ b/backend/utils/aws_helper.py @@ -1,3 +1,4 @@ +import io import os import boto3 from datetime import datetime @@ -33,10 +34,17 @@ def upload_file( Returns: Public URL of uploaded object """ - try: - file_obj.seek(0) + # ✅ CASE 1: bytes → convert to file-like + if isinstance(file_obj, bytes): + file_obj = io.BytesIO(file_obj) + + # ✅ CASE 2: UploadFile → use .file (sync object) + elif hasattr(file_obj, "file"): + file_obj = file_obj.file + # ❌ DO NOT use seek on async object + s3_client.upload_fileobj( file_obj, bucket_name, From c4f44530712116551c46a47c7b4bade46dd2f291 Mon Sep 17 00:00:00 2001 From: Ravindrayadav04 Date: Wed, 1 Apr 2026 23:19:42 +0530 Subject: [PATCH 05/12] bucket_name update --- backend/constants.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/constants.py b/backend/constants.py index 9a3c2e0..a0dd109 100644 --- a/backend/constants.py +++ b/backend/constants.py @@ -10,13 +10,14 @@ ENVIRONMENT = os.getenv("ENVIRONMENT", "development") GITHUB_TOKEN = os.getenv("GITHUB_TOKEN") API_KEY = os.getenv("INTERNAL_API_KEY", "abcd1234") +BUCKET_NAME = os.getenv("AWS_BUCKET_NAME") IS_PROD = ENVIRONMENT == "production" IS_DEV = ENVIRONMENT == "development" PROJECT_DIR = Path(__file__).parent -RELATIVE_GENERATED_FOLDER = "s3://tz-fabric-upload/uploaded/" +RELATIVE_GENERATED_FOLDER = f"s3://{BUCKET_NAME}/images/" ASSETS = PROJECT_DIR / "assets" UPLOAD_FOLDER_FABRIC = ASSETS / "search" IMAGE_DIR = ASSETS / "images" @@ -36,7 +37,7 @@ "api_url": "https://api.github.com/repos/recursivezero/tz-script/releases/tags/v3.5.0", }, } -DATABASE_PATH = str(PROJECT_DIR / "D:\\project\\tz-fabric\\backend\\lancedb") +DATABASE_PATH = str(PROJECT_DIR / "database") FABRIC_COLLECTION = "fabric_data" PROCESSING_TIMES_COLLECTION = "fabric_log" From cf828e9706629bc60efe10aa58f262b3eb0255e1 Mon Sep 17 00:00:00 2001 From: Ravindrayadav04 Date: Wed, 1 Apr 2026 23:41:34 +0530 Subject: [PATCH 06/12] compact folder --- tz-fabric.code-workspace | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tz-fabric.code-workspace b/tz-fabric.code-workspace index 6968858..ff4cc4e 100644 --- a/tz-fabric.code-workspace +++ b/tz-fabric.code-workspace @@ -134,6 +134,7 @@ "internal-terms": false // Disable the `internal-terms` dictionary }, "explorer.excludeGitIgnore": false, - "git.branchPrefix": "feature/TZF-2600XX" + "git.branchPrefix": "feature/TZF-2600XX", + "explorer.compactFolders": false } } \ No newline at end of file From a584c3834efac3d74a6e390a93e39af167d13ea5 Mon Sep 17 00:00:00 2001 From: Ravindrayadav04 Date: Fri, 3 Apr 2026 10:37:25 +0530 Subject: [PATCH 07/12] categories filtering --- backend/image_search/db/create_table.py | 2 +- backend/image_search/vector_search.py | 6 ++++-- backend/main.py | 2 +- backend/routes/routes_helper.py | 2 +- backend/routes/search.py | 17 ++++++++++++----- backend/utils/image_utils.py | 21 +++++++++++++++++++++ frontend/src/pages/FabricSearch.tsx | 19 ++++++++++++------- 7 files changed, 52 insertions(+), 17 deletions(-) diff --git a/backend/image_search/db/create_table.py b/backend/image_search/db/create_table.py index 9141464..3c4ecc7 100644 --- a/backend/image_search/db/create_table.py +++ b/backend/image_search/db/create_table.py @@ -70,7 +70,7 @@ def get_file_info(image_path: str) -> Optional[Dict[Any, Any]]: return None -ALLOWED_ROOTS = {"stock", "fabric", "design", "single", "group"} +ALLOWED_ROOTS = {"stock", "fabric", "design", "product","single","group"} def collect_image_data(root_folder: str) -> list: diff --git a/backend/image_search/vector_search.py b/backend/image_search/vector_search.py index a2313b7..0afd310 100644 --- a/backend/image_search/vector_search.py +++ b/backend/image_search/vector_search.py @@ -4,8 +4,9 @@ def run_vector_search( - table, schema, search_query: Any, limit: int = 6, category: List | None = None + table, schema, search_query: Any, limit: int = 6, category: List = [] ) -> Tuple[List[Any], List[str]]: + print(category) """Optimized vector search with same interface but faster performance. Args: @@ -24,7 +25,7 @@ def run_vector_search( # Perform the vector search where_clause = " OR ".join(f"tag == '{c}'" for c in (category or [])) - + print("WHERE CLAUSE:", where_clause) query = table.search(search_query) print(f"Initial search query constructed: {query}") # Debug log @@ -45,6 +46,7 @@ def run_vector_search( parts = full_path.rsplit("/", 2) if len(parts) >= 2: image_paths.append(f"{parts[-2]}/{parts[-1]}") + print("TAG VALUE:", getattr(result, "tag", None)) # Debug timing (comment out in production) search_time = time.perf_counter() - start_time diff --git a/backend/main.py b/backend/main.py index f34ff77..2ee141b 100644 --- a/backend/main.py +++ b/backend/main.py @@ -79,7 +79,7 @@ class MyApp(FastAPI): "https://lab.threadzip.com", "https://app.threadzip.com", "https://threadzip.com", - "https://recursivezero.github.io" + "https://recursivezero.github.io", ] # Select origins based on the environment diff --git a/backend/routes/routes_helper.py b/backend/routes/routes_helper.py index 951735e..499628a 100644 --- a/backend/routes/routes_helper.py +++ b/backend/routes/routes_helper.py @@ -58,7 +58,7 @@ def allowed_file(filename): return "." in name and name.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS -ALLOWED_CATEGORIES = {"stock", "fabric", "design", "single", "group"} +ALLOWED_CATEGORIES = {"stock", "fabric", "design", "product", "single", "group"} def sanitize(arr: Optional[List[str]]) -> List[str]: diff --git a/backend/routes/search.py b/backend/routes/search.py index b95f2e1..2200ef3 100644 --- a/backend/routes/search.py +++ b/backend/routes/search.py @@ -3,6 +3,7 @@ from pathlib import Path from typing import List, Optional +from utils.image_utils import parse_list, replace_with_multiple from fastapi import APIRouter, File, Form, HTTPException, Request, UploadFile from PIL import Image from routes.routes_helper import SearchResponse, sanitize @@ -48,9 +49,12 @@ async def image_search( try: # Handle JSON vs Form content_type = request.headers.get("content-type", "") - parsed_categories = category or [] + parsed_categories = parse_list(category) + sanitized_categories = sanitize(parsed_categories) + print("DEBUG sanitized_categories:", sanitized_categories) + final_categories= replace_with_multiple(sanitized_categories, target="product", replacements=["single","group"]) + print("DEBUG final_categories:", final_categories) - parsed_categories = sanitize(category) if "application/json" in content_type: body = await request.json() @@ -101,8 +105,10 @@ async def image_search( search_start = time.time() limit = limit or 20 _, image_paths = run_vector_search( - table, Fabric, image, limit=limit, category=parsed_categories + table, Fabric, image, limit=limit, category=final_categories ) + print("DEBUG image_paths:", image_paths) + print("DEBUG final_categories in image search:", final_categories) search_time = time.time() - search_start logThis.info( f"Vector search took {search_time:.4f}s", extra={"color": "green"} @@ -154,9 +160,10 @@ async def image_search( elif search_term: search_start = time.time() limit = limit or 20 - _, all_results = await run_vector_search( - table, Fabric, search_term, limit=limit, category=parsed_categories + _, all_results = run_vector_search( + table, Fabric, search_term, limit=limit, category=final_categories ) + print("final_categories in text search:", final_categories) search_time = time.time() - search_start logThis.info( diff --git a/backend/utils/image_utils.py b/backend/utils/image_utils.py index 5af316b..f64af20 100644 --- a/backend/utils/image_utils.py +++ b/backend/utils/image_utils.py @@ -11,3 +11,24 @@ def convert_image_to_base64(image) -> str: except Exception as e: print("Failed to convert image:", e) return "" + +# return [] or list of strings +def parse_list(value:str)-> list: + parsed_list = [] + if value is None or value == [""] or value == [] or value == "" or value == "null": + return [] + elif isinstance(value, str): + parsed_list = [value] + elif isinstance(value, list): + parsed_list = list(filter(None, value)) + return parsed_list + + +def replace_with_multiple(lst, target, replacements): + result = [] + for item in lst: + if item == target: + result.extend(replacements) + else: + result.append(item) + return result \ No newline at end of file diff --git a/frontend/src/pages/FabricSearch.tsx b/frontend/src/pages/FabricSearch.tsx index 0e7e3ee..5b744da 100644 --- a/frontend/src/pages/FabricSearch.tsx +++ b/frontend/src/pages/FabricSearch.tsx @@ -37,8 +37,8 @@ const CATEGORIES = [ { id: "stock", label: "Stock", icon: "📦" }, { id: "fabric", label: "Fabric", icon: "🧵" }, { id: "design", label: "Design", icon: "🎨" }, - { id: "single", label: "Single", icon: "🖼️" }, - { id: "group", label: "Group", icon: "👥" } + { id: "product", label: "Product", icon: "🖼️" }, + // { id: "group", label: "Group", icon: "👥" } ]; // ─── API helpers ───────────────────────────────────────────────────────────── @@ -64,6 +64,7 @@ function useSearch() { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [results, setResults] = useState([]); + const runImageSearch = useCallback(async (file: File, category?: string[], limit = 40) => { setLoading(true); @@ -72,9 +73,13 @@ function useSearch() { const form = new FormData(); form.append("file", file); form.append("limit", String(limit)); + console.log("FORM DATA CATEGORY:", category); + console.log("FILE:", file); + console.log("RUN IMAGE CATEGORY VALUE:", category); + console.log("TYPE:", typeof category); if (category?.length) { category.forEach((c) => { - form.append("category[]", c); + form.append("category", c); }); } const res = await fetch(`${API_BASE}/search`, { method: "POST", body: form }); @@ -101,7 +106,7 @@ function useSearch() { form.append("limit", String(limit)); if (category?.length) { category.forEach((c) => { - form.append("category[]", c); + form.append("category", c); }); } const res = await fetch(`${API_BASE}/search`, { method: "POST", body: form }); @@ -375,7 +380,7 @@ export default function Search() { setOriginalObjectUrl(f); setFile(f); setIsTextSearch(false); - await runImageSearch(f, undefined, searchLimit); + await runImageSearch(f, selectedCategories, searchLimit); } catch { setNotification({ message: "Could not auto-run search from URL.", type: "error" }); } finally { @@ -402,7 +407,7 @@ export default function Search() { setOriginalObjectUrl(f); setFile(f); setIsTextSearch(false); - await runImageSearch(f, undefined, searchLimit); + await runImageSearch(f, selectedCategories, searchLimit); } catch { setNotification({ message: "Could not auto-run search payload.", type: "error" }); } finally { @@ -682,7 +687,7 @@ export default function Search() { // Auto-run image search immediately after crop setNotification(null); try { - await runImageSearch(croppedFile, undefined, searchLimit); + await runImageSearch(croppedFile, selectedCategories, searchLimit); } catch { setNotification({ message: "Search failed.", type: "error" }); } From 5202b3a65412c0ec4acdfc6928ae3c05a583f92f Mon Sep 17 00:00:00 2001 From: Ravindrayadav04 Date: Fri, 3 Apr 2026 14:09:38 +0530 Subject: [PATCH 08/12] lint fix --- backend/image_search/db/create_table.py | 4 ++-- backend/image_search/vector_search.py | 2 +- backend/routes/search.py | 5 +++-- backend/utils/aws_helper.py | 2 +- backend/utils/image_utils.py | 11 ++++++----- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/backend/image_search/db/create_table.py b/backend/image_search/db/create_table.py index 3c4ecc7..d8336b8 100644 --- a/backend/image_search/db/create_table.py +++ b/backend/image_search/db/create_table.py @@ -70,7 +70,7 @@ def get_file_info(image_path: str) -> Optional[Dict[Any, Any]]: return None -ALLOWED_ROOTS = {"stock", "fabric", "design", "product","single","group"} +ALLOWED_ROOTS = {"stock", "fabric", "design", "product", "single", "group"} def collect_image_data(root_folder: str) -> list: @@ -281,7 +281,7 @@ def process_table( db = lancedb.connect(database) if not hasattr(db, "list_tables"): - db.list_tables = db.table_names + db.list_tables = db.table_names logger.info(TABLE_MESSAGES.info.available_tables.format(tables=db.list_tables())) logger.info(TABLE_MESSAGES.info.looking_for_table.format(table_name=table_name)) logger.info( diff --git a/backend/image_search/vector_search.py b/backend/image_search/vector_search.py index 0afd310..be2497c 100644 --- a/backend/image_search/vector_search.py +++ b/backend/image_search/vector_search.py @@ -46,7 +46,7 @@ def run_vector_search( parts = full_path.rsplit("/", 2) if len(parts) >= 2: image_paths.append(f"{parts[-2]}/{parts[-1]}") - print("TAG VALUE:", getattr(result, "tag", None)) + print("TAG VALUE:", getattr(result, "tag", None)) # Debug timing (comment out in production) search_time = time.perf_counter() - start_time diff --git a/backend/routes/search.py b/backend/routes/search.py index 2200ef3..c1053d6 100644 --- a/backend/routes/search.py +++ b/backend/routes/search.py @@ -52,10 +52,11 @@ async def image_search( parsed_categories = parse_list(category) sanitized_categories = sanitize(parsed_categories) print("DEBUG sanitized_categories:", sanitized_categories) - final_categories= replace_with_multiple(sanitized_categories, target="product", replacements=["single","group"]) + final_categories = replace_with_multiple( + sanitized_categories, target="product", replacements=["single", "group"] + ) print("DEBUG final_categories:", final_categories) - if "application/json" in content_type: body = await request.json() search_term = body.get("search_term") diff --git a/backend/utils/aws_helper.py b/backend/utils/aws_helper.py index b55f3a3..ef8f904 100644 --- a/backend/utils/aws_helper.py +++ b/backend/utils/aws_helper.py @@ -44,7 +44,7 @@ def upload_file( file_obj = file_obj.file # ❌ DO NOT use seek on async object - + s3_client.upload_fileobj( file_obj, bucket_name, diff --git a/backend/utils/image_utils.py b/backend/utils/image_utils.py index f64af20..93335df 100644 --- a/backend/utils/image_utils.py +++ b/backend/utils/image_utils.py @@ -12,15 +12,16 @@ def convert_image_to_base64(image) -> str: print("Failed to convert image:", e) return "" + # return [] or list of strings -def parse_list(value:str)-> list: +def parse_list(value: str) -> list: parsed_list = [] if value is None or value == [""] or value == [] or value == "" or value == "null": - return [] + return [] elif isinstance(value, str): - parsed_list = [value] + parsed_list = [value] elif isinstance(value, list): - parsed_list = list(filter(None, value)) + parsed_list = list(filter(None, value)) return parsed_list @@ -31,4 +32,4 @@ def replace_with_multiple(lst, target, replacements): result.extend(replacements) else: result.append(item) - return result \ No newline at end of file + return result From 96db6420fa9cb087ed63fb11ae45964ae64a610b Mon Sep 17 00:00:00 2001 From: Ravindrayadav04 Date: Fri, 3 Apr 2026 21:31:53 +0530 Subject: [PATCH 09/12] fixed product catgeory --- backend/image_search/db/create_table.py | 2 +- backend/image_search/vector_search.py | 5 ++--- backend/routes/search.py | 21 +++++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/backend/image_search/db/create_table.py b/backend/image_search/db/create_table.py index d8336b8..e6fbf89 100644 --- a/backend/image_search/db/create_table.py +++ b/backend/image_search/db/create_table.py @@ -70,7 +70,7 @@ def get_file_info(image_path: str) -> Optional[Dict[Any, Any]]: return None -ALLOWED_ROOTS = {"stock", "fabric", "design", "product", "single", "group"} +ALLOWED_ROOTS = {"stock", "fabric", "design", "product"} def collect_image_data(root_folder: str) -> list: diff --git a/backend/image_search/vector_search.py b/backend/image_search/vector_search.py index be2497c..77d8ec6 100644 --- a/backend/image_search/vector_search.py +++ b/backend/image_search/vector_search.py @@ -22,7 +22,7 @@ def run_vector_search( """ # Start timing start_time = time.perf_counter() - + # Perform the vector search where_clause = " OR ".join(f"tag == '{c}'" for c in (category or [])) print("WHERE CLAUSE:", where_clause) @@ -46,8 +46,7 @@ def run_vector_search( parts = full_path.rsplit("/", 2) if len(parts) >= 2: image_paths.append(f"{parts[-2]}/{parts[-1]}") - print("TAG VALUE:", getattr(result, "tag", None)) - + # Debug timing (comment out in production) search_time = time.perf_counter() - start_time print(f"Vector search executed in {search_time:.2f}s") diff --git a/backend/routes/search.py b/backend/routes/search.py index c1053d6..d960fc1 100644 --- a/backend/routes/search.py +++ b/backend/routes/search.py @@ -3,7 +3,7 @@ from pathlib import Path from typing import List, Optional -from utils.image_utils import parse_list, replace_with_multiple +from utils.image_utils import parse_list from fastapi import APIRouter, File, Form, HTTPException, Request, UploadFile from PIL import Image from routes.routes_helper import SearchResponse, sanitize @@ -52,12 +52,13 @@ async def image_search( parsed_categories = parse_list(category) sanitized_categories = sanitize(parsed_categories) print("DEBUG sanitized_categories:", sanitized_categories) - final_categories = replace_with_multiple( - sanitized_categories, target="product", replacements=["single", "group"] - ) - print("DEBUG final_categories:", final_categories) + # final_categories = replace_with_multiple( + # sanitized_categories, target="product", replacements=["single", "group"] + # ) + # print("DEBUG final_categories:", final_categories) - if "application/json" in content_type: + + if "application/json" in content_type: body = await request.json() search_term = body.get("search_term") limit = body.get("limit", 5) @@ -106,10 +107,10 @@ async def image_search( search_start = time.time() limit = limit or 20 _, image_paths = run_vector_search( - table, Fabric, image, limit=limit, category=final_categories + table, Fabric, image, limit=limit, category=sanitized_categories ) print("DEBUG image_paths:", image_paths) - print("DEBUG final_categories in image search:", final_categories) + print("DEBUG sanitized_categories in image search:", sanitized_categories) search_time = time.time() - search_start logThis.info( f"Vector search took {search_time:.4f}s", extra={"color": "green"} @@ -162,9 +163,9 @@ async def image_search( search_start = time.time() limit = limit or 20 _, all_results = run_vector_search( - table, Fabric, search_term, limit=limit, category=final_categories + table, Fabric, search_term, limit=limit, category=sanitized_categories ) - print("final_categories in text search:", final_categories) + print("sanitized_categories in text search:", sanitized_categories) search_time = time.time() - search_start logThis.info( From b3e0580fafeb468eb7615ef7d69e23f4a7bb4df5 Mon Sep 17 00:00:00 2001 From: RecursiveZero Date: Sat, 4 Apr 2026 23:02:59 +0530 Subject: [PATCH 10/12] [TZF-260026]: update UI for fabric Search --- .vscode/project-words.txt | 1 + backend/image_search/vector_search.py | 19 +- backend/poetry.lock | 12 +- backend/routes/search.py | 3 +- backend/utils/image_utils.py | 3 +- frontend/src/assets/styles/FabricSearch.css | 1584 +++++++++++-------- frontend/src/pages/FabricSearch.tsx | 1489 +++++++++-------- 7 files changed, 1812 insertions(+), 1299 deletions(-) diff --git a/.vscode/project-words.txt b/.vscode/project-words.txt index 5d4995d..5af201f 100644 --- a/.vscode/project-words.txt +++ b/.vscode/project-words.txt @@ -6,6 +6,7 @@ appleboy colour easyocr embedder +fabricai lancedb mcpserver metadatas diff --git a/backend/image_search/vector_search.py b/backend/image_search/vector_search.py index 77d8ec6..879c552 100644 --- a/backend/image_search/vector_search.py +++ b/backend/image_search/vector_search.py @@ -22,7 +22,7 @@ def run_vector_search( """ # Start timing start_time = time.perf_counter() - + # Perform the vector search where_clause = " OR ".join(f"tag == '{c}'" for c in (category or [])) print("WHERE CLAUSE:", where_clause) @@ -43,12 +43,19 @@ def run_vector_search( image_uris.append(result.image_uri) # Optimized path processing full_path = result.image_uri.replace("\\", "/") - parts = full_path.rsplit("/", 2) - if len(parts) >= 2: - image_paths.append(f"{parts[-2]}/{parts[-1]}") - + # parts = full_path.rsplit("/", 2) + parts = full_path.split("/") + print(full_path, parts) + + if "product" in parts: + idx = parts.index("product") + image_paths.append("/".join(parts[idx:])) + else: + image_paths.append("/".join(parts[-2:])) + # if len(parts) >= 2: + # image_paths.append(f"{parts[-2]}/{parts[-1]}") + # Debug timing (comment out in production) search_time = time.perf_counter() - start_time print(f"Vector search executed in {search_time:.2f}s") - return image_uris, image_paths diff --git a/backend/poetry.lock b/backend/poetry.lock index f802408..0473221 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "annotated-doc" @@ -2584,7 +2584,7 @@ files = [ [package.dependencies] attrs = ">=22.2.0" -jsonschema-specifications = ">=2023.3.6" +jsonschema-specifications = ">=2023.03.6" referencing = ">=0.28.4" rpds-py = ">=0.25.0" @@ -2726,7 +2726,7 @@ files = [ ] [package.dependencies] -certifi = ">=14.5.14" +certifi = ">=14.05.14" durationpy = ">=0.7" python-dateutil = ">=2.5.3" pyyaml = ">=5.4.1" @@ -6615,10 +6615,10 @@ files = [ ] [package.dependencies] -botocore = ">=1.37.4,<2.0a0" +botocore = ">=1.37.4,<2.0a.0" [package.extras] -crt = ["botocore[crt] (>=1.37.4,<2.0a0)"] +crt = ["botocore[crt] (>=1.37.4,<2.0a.0)"] [[package]] name = "safetensors" @@ -8377,7 +8377,7 @@ files = [ ] [package.extras] -cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and python_version < \"3.14\"", "cffi (>=2.0.0b0) ; platform_python_implementation != \"PyPy\" and python_version >= \"3.14\""] +cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and python_version < \"3.14\"", "cffi (>=2.0.0b) ; platform_python_implementation != \"PyPy\" and python_version >= \"3.14\""] [extras] extra = ["chromadb", "pymongo", "sentencepiece", "transformers"] diff --git a/backend/routes/search.py b/backend/routes/search.py index d960fc1..d8cc5b0 100644 --- a/backend/routes/search.py +++ b/backend/routes/search.py @@ -57,8 +57,7 @@ async def image_search( # ) # print("DEBUG final_categories:", final_categories) - - if "application/json" in content_type: + if "application/json" in content_type: body = await request.json() search_term = body.get("search_term") limit = body.get("limit", 5) diff --git a/backend/utils/image_utils.py b/backend/utils/image_utils.py index 93335df..bbb96a1 100644 --- a/backend/utils/image_utils.py +++ b/backend/utils/image_utils.py @@ -1,5 +1,6 @@ import base64 import io +from typing import Any def convert_image_to_base64(image) -> str: @@ -14,7 +15,7 @@ def convert_image_to_base64(image) -> str: # return [] or list of strings -def parse_list(value: str) -> list: +def parse_list(value: Any) -> list: parsed_list = [] if value is None or value == [""] or value == [] or value == "" or value == "null": return [] diff --git a/frontend/src/assets/styles/FabricSearch.css b/frontend/src/assets/styles/FabricSearch.css index cdb0b9b..d9afb19 100644 --- a/frontend/src/assets/styles/FabricSearch.css +++ b/frontend/src/assets/styles/FabricSearch.css @@ -1,112 +1,102 @@ /* ───────────────────────────────────────────────────────────────────────────── - FabricSearch.css — ThreadZip visual search styles + FabricSearch.css — ThreadZip visual search | BEM architecture + Blocks: fabric-search · hero · search-bar · limit-slider · category-picker + result-grid · result-card · results-section · image-preview + db-panel · settings-panel · crop-drawer · lightbox · pagination · btn ───────────────────────────────────────────────────────────────────────────── */ -/* ── CSS variables ─────────────────────────────────────────────────────────── */ +/* ── Design tokens ─────────────────────────────────────────────────────────── */ :root { - --tz-bg: #0a0807; - --tz-surf: rgba(14, 11, 9, 0.84); - --tz-surf2: rgba(20, 16, 12, 0.94); - --tz-bdr: rgba(255, 255, 255, 0.07); - --tz-bdr2: rgba(255, 255, 255, 0.11); - --tz-bdr-g: rgba(201, 169, 110, 0.30); - --tz-txt: #f0ede8; - --tz-muted: #6b6259; - --tz-acc: #e8d5b0; - --tz-acc2: #c9a96e; - --tz-green: #22c55e; - --tz-blue: #3b82f6; - --tz-red: #ef4444; - --ff: 'Syne', sans-serif; - --fm: 'DM Mono', monospace; + --color-bg: #0a0807; + --color-surface: rgba(14, 11, 9, 0.84); + --color-surface-2: rgba(20, 16, 12, 0.94); + --color-border: rgba(255, 255, 255, 0.07); + --color-border-2: rgba(255, 255, 255, 0.11); + --color-border-gold: rgba(201, 169, 110, 0.30); + --color-text: #f0ede8; + --color-muted: #6b6259; + --color-accent: #e8d5b0; + --color-accent-2: #0f7234; + --color-green: #22c55e; + --color-blue: #3b82f6; + --color-red: #ef4444; + + --font-sans: 'Syne', sans-serif; + --font-mono: 'DM Mono', monospace; + + --sticky-bar-height: 68px; + --radius-sm: 8px; + --radius-md: 10px; + --radius-lg: 14px; + --radius-xl: 20px; + + --transition-fast: 0.15s ease; + --transition-base: 0.18s ease; + --transition-slow: 0.22s ease; } *, *::before, -*::after { - box-sizing: border-box; -} +*::after { box-sizing: border-box; } -/* ── Keyframes ─────────────────────────────────────────────────────────────── */ -@keyframes tz-fadeup { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } +/* ── Keyframes ─────────────────────────────────────────────────────────────── */ +@keyframes anim-fade-up { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } } -@keyframes tz-fadein { - from { - opacity: 0; - } - - to { - opacity: 1; - } +@keyframes anim-fade-in { + from { opacity: 0; } + to { opacity: 1; } } -@keyframes tz-spin { - to { - transform: rotate(360deg); - } +@keyframes anim-spin { + to { transform: rotate(360deg); } } -@keyframes tz-pulse { - 0% { - box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.55); - } - - 70% { - box-shadow: 0 0 0 7px rgba(34, 197, 94, 0); - } - - 100% { - box-shadow: 0 0 0 0 rgba(34, 197, 94, 0); - } +@keyframes anim-pulse { + 0% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.55); } + 70% { box-shadow: 0 0 0 7px rgba(34, 197, 94, 0); } + 100% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0); } } -@keyframes tz-shimmer { - 0% { - background-position: 0% 50%; - } - - 50% { - background-position: 100% 50%; - } - - 100% { - background-position: 0% 50%; - } +@keyframes anim-shimmer { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } } -@keyframes tz-cardin { - from { - opacity: 0; - transform: translateY(14px) scale(0.97); - } +@keyframes anim-card-in { + from { opacity: 0; transform: translateY(14px) scale(0.97); } + to { opacity: 1; transform: translateY(0) scale(1); } +} - to { - opacity: 1; - transform: translateY(0) scale(1); - } +@keyframes anim-slide-down { + from { opacity: 0; transform: translateY(-100%); } + to { opacity: 1; transform: translateY(0); } } -/* ── Root & page shell ─────────────────────────────────────────────────────── */ -.tz-root { - font-family: var(--ff); - color: var(--tz-txt); + +/* ══════════════════════════════════════════════════════════════════════════════ + BLOCK: fabric-search (page root) +══════════════════════════════════════════════════════════════════════════════ */ +.fabric-search { + font-family: var(--font-sans); + color: var(--color-text); min-height: 100vh; position: relative; overflow-x: hidden; } -.search-container { +/* Modifier: push body down when sticky bar is present */ +.fabric-search--has-results { + padding-top: var(--sticky-bar-height); +} + +/* Element: main scrollable content area */ +.fabric-search__body { position: relative; z-index: 1; max-width: 1060px; @@ -114,153 +104,154 @@ padding: 32px 24px 100px; } -/* ── Glass utility ─────────────────────────────────────────────────────────── */ -.tz-glass { - background: var(--tz-surf); - border: 1px solid var(--tz-bdr); - backdrop-filter: blur(18px); - -webkit-backdrop-filter: blur(18px); +/* Modifier: full-bleed when results are visible */ +.fabric-search__body--results { + max-width: none; + margin-block: 1.5rem; } -/* ── DB panel ──────────────────────────────────────────────────────────────── */ -.tz-db-panel { +/* Element: header + settings row */ +.fabric-search__top-bar { display: flex; - align-items: center; - gap: 20px; + align-items: flex-start; + justify-content: space-between; flex-wrap: wrap; - padding: 13px 20px; - border-radius: 14px; - margin-bottom: 40px; - position: relative; - overflow: hidden; + gap: 12px; } -.tz-db-panel::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 1px; - background: linear-gradient(90deg, transparent, var(--tz-bdr-g), transparent); +.fabric-search__settings { + position: relative; + top: 1rem; } -.tz-db-header { - display: flex; - align-items: center; - gap: 8px; - flex-shrink: 0; +/* Element: error / empty states */ +.fabric-search__error { + font-family: var(--font-mono); + font-size: 13px; + color: #f87171; + padding: 12px 16px; + background: rgba(239, 68, 68, 0.07); + border: 1px solid rgba(239, 68, 68, 0.18); + border-radius: var(--radius-md); + margin-bottom: 16px; } -.tz-db-pulse { - width: 7px; - height: 7px; - border-radius: 50%; - background: var(--tz-green); - animation: tz-pulse 2s infinite; - flex-shrink: 0; +.fabric-search__empty { + font-family: var(--font-mono); + font-size: 13px; + color: var(--color-muted); + text-align: center; + padding: 60px 0; + letter-spacing: 0.06em; } -.tz-db-label { - font-family: var(--fm); - font-size: 10px; - letter-spacing: .15em; - text-transform: uppercase; - color: var(--tz-muted); -} -.tz-db-actions { - display: flex; - gap: 10px; - flex-wrap: wrap; +/* ══════════════════════════════════════════════════════════════════════════════ + UTILITY: glass surface (shared by multiple blocks) +══════════════════════════════════════════════════════════════════════════════ */ +.glass { + background: var(--color-surface); + border: 1px solid var(--color-border); + backdrop-filter: blur(18px); + -webkit-backdrop-filter: blur(18px); } -.tz-db-btn { + +/* ══════════════════════════════════════════════════════════════════════════════ + BLOCK: btn (reusable button primitive) +══════════════════════════════════════════════════════════════════════════════ */ +.btn { display: inline-flex; align-items: center; gap: 7px; - padding: 8px 18px; - border-radius: 8px; - font-family: var(--ff); - font-size: 12px; - font-weight: 600; - letter-spacing: .04em; + padding: 13px 22px; + border-radius: var(--radius-md); + font-family: var(--font-sans); + font-size: 13px; + font-weight: 700; + letter-spacing: 0.04em; cursor: pointer; - border: 1px solid transparent; - transition: all .18s; + border: none; + outline: none; + transition: all var(--transition-base); white-space: nowrap; + text-align: center; } -.tz-db-btn:disabled { - opacity: .35; +.btn:disabled { + opacity: 0.35; cursor: not-allowed; + transform: none !important; + animation: none !important; } -.tz-db-btn:not(:disabled):hover { - transform: translateY(-2px); - filter: brightness(1.14); +.btn:active { transform: translateY(0) !important; } + +/* Modifier: primary (gold shimmer) */ +.btn--primary { + background: linear-gradient(135deg, #b8934a, #e8d5b0, #c9a96e, #b8934a); + background-size: 250% 250%; + color: #0a0805; + box-shadow: 0 4px 20px rgba(201, 169, 110, 0.28); + animation: anim-shimmer 3.5s linear infinite; } -.tz-db-create { - background: rgba(34, 197, 94, .10); - color: #4ade80; - border-color: rgba(34, 197, 94, .22); +.btn--primary:not(:disabled):hover { + transform: translateY(-2px); + box-shadow: 0 8px 28px rgba(201, 169, 110, 0.42); } -.tz-db-update { - background: rgba(59, 130, 246, .10); - color: #60a5fa; - border-color: rgba(59, 130, 246, .22); +/* Modifier: ghost */ +.btn--ghost { + background: transparent; + color: var(--color-text); + border: 1px solid var(--color-border-2); + backdrop-filter: blur(10px); } -.tz-db-icon { - font-size: 14px; - line-height: 1; +.btn--ghost:not(:disabled):hover { + border-color: var(--color-border-gold); + transform: translateY(-2px); } -.tz-spinner { - width: 12px; - height: 12px; - border: 2px solid rgba(255, 255, 255, .2); - border-top-color: currentColor; - border-radius: 50%; - animation: tz-spin .6s linear infinite; - display: inline-block; - flex-shrink: 0; +/* Modifier: outline */ +.btn--outline { + background: transparent; + color: var(--color-accent); + border: 1px solid var(--color-border-gold); } -.tz-db-notif { - font-family: var(--fm); - font-size: 11px; - padding: 7px 12px; - border-radius: 7px; +.btn--outline:not(:disabled):hover { + background: rgba(232, 213, 176, 0.07); + transform: translateY(-2px); } -.tz-db-notif.success { - background: rgba(34, 197, 94, .08); - color: #4ade80; - border: 1px solid rgba(34, 197, 94, .18); +/* Modifier: small */ +.btn--sm { + padding: 8px 14px; + font-size: 12px; } -.tz-db-notif.error { - background: rgba(239, 68, 68, .08); - color: #f87171; - border: 1px solid rgba(239, 68, 68, .18); + +/* ══════════════════════════════════════════════════════════════════════════════ + BLOCK: hero +══════════════════════════════════════════════════════════════════════════════ */ +.hero { + animation: anim-fade-up 0.7s ease both; } -/* ── Hero ──────────────────────────────────────────────────────────────────── */ -.hero-area { +/* Element: header text group */ +.hero__header { text-align: center; padding: 52px 0 60px; - animation: tz-fadeup .7s ease both; } -.hero-eyebrow { - font-family: var(--fm); +.hero__eyebrow { + font-family: var(--font-mono); font-size: 10px; - letter-spacing: .24em; + letter-spacing: 0.24em; text-transform: uppercase; - color: var(--tz-acc2); + color: var(--color-accent-2); margin-bottom: 20px; display: flex; align-items: center; @@ -268,47 +259,41 @@ gap: 12px; } -.hero-eyebrow::before, -.hero-eyebrow::after { +.hero__eyebrow::before, +.hero__eyebrow::after { content: ''; width: 36px; height: 1px; } +.hero__eyebrow::before { background: linear-gradient(90deg, transparent, var(--color-accent-2)); } +.hero__eyebrow::after { background: linear-gradient(90deg, var(--color-accent-2), transparent); } -.hero-eyebrow::before { - background: linear-gradient(90deg, transparent, var(--tz-acc2)); -} - -.hero-eyebrow::after { - background: linear-gradient(90deg, var(--tz-acc2), transparent); -} - -.hero-title { +.hero__title { font-size: clamp(34px, 5.5vw, 66px); font-weight: 800; line-height: 1.06; - letter-spacing: -.03em; + letter-spacing: -0.03em; color: #636cd6; margin: 0 0 12px; } -.tz-gold { +.hero__title-accent { background: linear-gradient(130deg, #da4545 0%, #588664 40%, #e15dce 80%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } -.hero-sub { - font-family: var(--fm); +.hero__subtitle { + font-family: var(--font-mono); font-size: 13px; - color: var(--tz-muted); + color: var(--color-muted); margin-bottom: 44px; - letter-spacing: .03em; + letter-spacing: 0.03em; } -/* ── Search controls ───────────────────────────────────────────────────────── */ -.tz-sc { +/* Element: controls container */ +.hero__search-controls { display: flex; flex-direction: column; align-items: center; @@ -317,88 +302,110 @@ margin: 0 auto; } -.tz-row { +.hero__search-row { display: flex; gap: 8px; width: 100%; } -.tz-input { +.hero__search-input { flex: 1; padding: 13px 17px; - border-radius: 10px; - border: 1px solid var(--tz-bdr2); - background: var(--tz-surf); - color: var(--tz-txt); - font-family: var(--ff); + border-radius: var(--radius-md); + border: 1px solid var(--color-border-2); + background: var(--color-surface); + color: var(--color-text); + font-family: var(--font-sans); font-size: 14px; outline: none; backdrop-filter: blur(12px); - transition: border-color .18s, box-shadow .18s; + transition: border-color var(--transition-base), box-shadow var(--transition-base); } -.tz-input::placeholder { - color: var(--tz-muted); +.hero__search-input::placeholder { color: var(--color-muted); } + +.hero__search-input:focus { + border-color: var(--color-border-gold); + box-shadow: 0 0 0 3px rgba(201, 169, 110, 0.08); } -.tz-input:focus { - border-color: var(--tz-bdr-g); - box-shadow: 0 0 0 3px rgba(201, 169, 110, .08); +.hero__divider { + display: flex; + align-items: center; + gap: 12px; + width: 100%; + color: var(--color-muted); + font-family: var(--font-mono); + font-size: 1rem; + letter-spacing: 0.15em; + text-transform: uppercase; } -/* ── Result-page search bar ─────────────────────────────────────────────────── */ -.results-search-bar .tz-row { - max-width: 420px; +.hero__divider::before, +.hero__divider::after { + content: ''; + flex: 1; + height: 1px; + background: var(--color-border); } -.results-search-bar .tz-input { - font-size: 13px; - padding: 10px 14px; +.hero__file-input { + position: absolute; + opacity: 0; + pointer-events: none; + width: 0; + height: 0; } -.results-search-bar .tz-btn { - padding: 10px 14px; - font-size: 13px; +.hero__upload-btn { + width: 100%; + justify-content: center; +} + +.hero__categories { + margin-top: 32px; } -/* ── Limit slider ──────────────────────────────────────────────────────────── */ -.tz-lim { + +/* ══════════════════════════════════════════════════════════════════════════════ + BLOCK: limit-slider +══════════════════════════════════════════════════════════════════════════════ */ +.limit-slider { display: flex; align-items: center; gap: 10px; width: 100%; + padding: 1rem; } -.tz-lim-lbl { - font-family: var(--fm); +.limit-slider__label { + font-family: var(--font-mono); font-size: 10px; - color: var(--tz-muted); - letter-spacing: .12em; + color: var(--color-muted); + letter-spacing: 0.12em; text-transform: uppercase; white-space: nowrap; } -.tz-lim-trk { +.limit-slider__track { flex: 1; position: relative; height: 3px; - background: rgba(255, 255, 255, .06); + background: rgba(255, 255, 255, 0.06); border-radius: 4px; cursor: pointer; } -.tz-lim-fill { +.limit-slider__fill { position: absolute; - left: 0; - top: 0; - bottom: 0; - background: linear-gradient(90deg, var(--tz-acc2), var(--tz-acc)); + left: 0; top: 0; bottom: 0; + background: linear-gradient(90deg, var(--color-accent-2), var(--color-accent)); border-radius: 4px; pointer-events: none; - transition: width .08s; + transition: width 0.08s; } -.tz-lim-inp { +.limit-slider__input { -webkit-appearance: none; appearance: none; position: absolute; @@ -410,661 +417,854 @@ margin: 0; } -.tz-lim-inp::-webkit-slider-thumb { +.limit-slider__input::-webkit-slider-thumb { -webkit-appearance: none; width: 15px; height: 15px; border-radius: 50%; - background: var(--tz-acc); - border: 2px solid var(--tz-bg); - box-shadow: 0 0 0 2px rgba(232, 213, 176, .25); + background: var(--color-accent); + border: 2px solid var(--color-bg); + box-shadow: 0 0 0 2px rgba(232, 213, 176, 0.25); cursor: pointer; } -.tz-lim-inp::-moz-range-thumb { +.limit-slider__input::-moz-range-thumb { width: 15px; height: 15px; border-radius: 50%; - background: var(--tz-acc); - border: 2px solid var(--tz-bg); + background: var(--color-accent); + border: 2px solid var(--color-bg); cursor: pointer; } -.tz-lim-val { - font-family: var(--fm); +.limit-slider__value { + font-family: var(--font-mono); font-size: 13px; font-weight: 500; - color: var(--tz-acc); + color: var(--color-accent); min-width: 28px; text-align: right; } -/* ── Divider ───────────────────────────────────────────────────────────────── */ -.tz-div { - display: flex; - align-items: center; - gap: 12px; - width: 100%; - color: var(--tz-muted); - font-family: var(--fm); - font-size: 1rem; - letter-spacing: .15em; - text-transform: uppercase; -} - -.tz-div::before, -.tz-div::after { - content: ''; - flex: 1; - height: 1px; - background: var(--tz-bdr); -} -/* ── Category picker ───────────────────────────────────────────────────────── */ -.tz-cat-picker { +/* ══════════════════════════════════════════════════════════════════════════════ + BLOCK: category-picker +══════════════════════════════════════════════════════════════════════════════ */ +.category-picker { padding: 16px 18px; - border-radius: 14px; + border-radius: var(--radius-lg); + background: var(--color-surface); + border: 1px solid var(--color-border); + backdrop-filter: blur(18px); + -webkit-backdrop-filter: blur(18px); position: relative; overflow: hidden; - animation: tz-fadeup .5s ease both; + animation: anim-fade-up 0.5s ease both; } -.tz-cat-picker::before { +.category-picker::before { content: ''; position: absolute; - top: 0; - left: 0; - right: 0; + top: 0; left: 0; right: 0; height: 1px; - background: linear-gradient(90deg, transparent, var(--tz-bdr-g), transparent); + background: linear-gradient(90deg, transparent, var(--color-border-gold), transparent); +} + +/* Modifier: compact (used inside results panel) */ +.category-picker--compact { + padding: 12px 16px; } -.tz-cat-header { +/* Element: header row */ +.category-picker__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; } -.tz-cat-title { - font-family: var(--fm); +.category-picker__title { + font-family: var(--font-mono); font-size: 10px; font-weight: 500; - letter-spacing: .18em; + letter-spacing: 0.18em; text-transform: uppercase; - color: var(--tz-muted); + color: var(--color-muted); } -.tz-cat-all-btn { - font-family: var(--fm); +.category-picker__toggle-all { + font-family: var(--font-mono); font-size: 10px; - letter-spacing: .1em; + letter-spacing: 0.1em; text-transform: uppercase; - color: var(--tz-acc2); + color: var(--color-accent-2); background: none; border: none; cursor: pointer; padding: 0; - transition: color .15s; + transition: color var(--transition-fast); } -.tz-cat-all-btn:hover { - color: var(--tz-acc); -} +.category-picker__toggle-all:hover { color: var(--color-accent); } -.tz-cat-grid { +/* Element: chip grid */ +.category-picker__grid { display: flex; flex-wrap: wrap; gap: 7px; } -.tz-cat-chip { +.category-picker__chip { display: inline-flex; align-items: center; gap: 5px; padding: 7px 12px; - border-radius: 8px; - font-family: var(--ff); + border-radius: var(--radius-sm); + font-family: var(--font-sans); font-size: 12px; font-weight: 600; - letter-spacing: .02em; + letter-spacing: 0.02em; cursor: pointer; - border: 1px solid var(--tz-bdr2); - background: rgba(255, 255, 255, .03); - color: var(--tz-muted); - transition: all .18s; + border: 1px solid var(--color-border-2); + background: rgba(255, 255, 255, 0.03); + color: var(--color-muted); + transition: all var(--transition-base); } -.tz-cat-chip:hover { - border-color: var(--tz-bdr-g); - color: var(--tz-acc); - background: rgba(201, 169, 110, .06); +.category-picker__chip:hover { + border-color: var(--color-border-gold); + color: var(--color-accent); + background: rgba(201, 169, 110, 0.06); } -.tz-cat-active { - border-color: var(--tz-bdr-g) !important; - background: rgba(201, 169, 110, .11) !important; - color: var(--tz-acc) !important; - box-shadow: 0 0 12px rgba(201, 169, 110, .10); +/* Modifier: active chip */ +.category-picker__chip--active { + border-color: var(--color-border-gold) !important; + background: rgba(201, 169, 110, 0.11) !important; + color: var(--color-accent) !important; + box-shadow: 0 0 12px rgba(201, 169, 110, 0.10); } -.tz-cat-check { +.category-picker__chip-check { font-size: 10px; font-weight: 800; min-width: 10px; - color: var(--tz-acc2); + color: var(--color-accent-2); } -.tz-cat-icon { - font-size: 14px; - line-height: 1; +.category-picker__chip-icon { font-size: 14px; line-height: 1; } +.category-picker__chip-label { white-space: nowrap; } + + +/* ══════════════════════════════════════════════════════════════════════════════ + BLOCK: search-bar (sticky bar after results appear) +══════════════════════════════════════════════════════════════════════════════ */ +.search-bar { + /* base — not sticky */ } -.tz-cat-lbl { - white-space: nowrap; +/* Modifier: sticky (fixed at top) */ +.search-bar--sticky { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 200; + height: var(--sticky-bar-height); + background: rgba(10, 8, 7, 0.92); + border-bottom: 1px solid var(--color-border-gold); + backdrop-filter: blur(24px); + -webkit-backdrop-filter: blur(24px); + animation: anim-slide-down 0.22s ease both; } -/* Active filter badges in results */ -.tz-af { +/* Element: inner wrapper */ +.search-bar__inner { + height: 100%; + max-width: 1400px; + margin: 0 auto; + padding: 0 24px; display: flex; - gap: 6px; - flex-wrap: wrap; align-items: center; - margin-top: 6px; -} - -.tz-badge { - font-family: var(--fm); - font-size: 10px; - padding: 3px 9px; - border-radius: 20px; - background: rgba(201, 169, 110, .10); - border: 1px solid rgba(201, 169, 110, .22); - color: var(--tz-acc2); - letter-spacing: .08em; - text-transform: uppercase; + gap: 12px; } -.tz-fin { - font-family: var(--fm); - font-size: 10px; - color: var(--tz-muted); - letter-spacing: .1em; - text-transform: uppercase; +/* Element: text search row */ +.search-bar__text-row { + flex: 1; + display: flex; + align-items: center; + gap: 8px; + min-width: 0; } -/* ── Buttons ───────────────────────────────────────────────────────────────── */ -.tz-btn { - display: inline-flex; +.search-bar__input-wrap { + flex: 1; + position: relative; + display: flex; align-items: center; - gap: 7px; - padding: 13px 22px; - border-radius: 10px; - font-family: var(--ff); - font-size: 13px; - font-weight: 700; - letter-spacing: .04em; - cursor: pointer; - border: none; - outline: none; - transition: all .18s; - white-space: nowrap; + min-width: 0; } -.tz-btn-primary { - background: linear-gradient(135deg, #b8934a, #e8d5b0, #c9a96e, #b8934a); - background-size: 250% 250%; - color: #0a0805; - box-shadow: 0 4px 20px rgba(201, 169, 110, .28); - animation: tz-shimmer 3.5s linear infinite; +.search-bar__input-icon { + position: absolute; + left: 13px; + font-size: 14px; + pointer-events: none; + z-index: 1; + opacity: 0.5; } -.tz-btn-primary:not(:disabled):hover { - transform: translateY(-2px); - box-shadow: 0 8px 28px rgba(201, 169, 110, .42); +.search-bar__input { + flex: 1; + padding: 10px 36px 10px 38px; + border-radius: var(--radius-md); + border: 1px solid var(--color-border-2); + background: var(--color-surface); + color: var(--color-text); + font-family: var(--font-sans); + font-size: 14px; + outline: none; + backdrop-filter: blur(12px); + transition: border-color var(--transition-base), box-shadow var(--transition-base); + width: 100%; } -.tz-btn-ghost { - border: 1px solid var(--tz-bdr2); - backdrop-filter: blur(10px); - font-size: 1rem; -} +.search-bar__input::placeholder { color: var(--color-muted); } -.tz-btn-ghost:not(:disabled):hover { - border-color: var(--tz-bdr-g); - transform: translateY(-2px); +.search-bar__input:focus { + border-color: var(--color-border-gold); + box-shadow: 0 0 0 3px rgba(201, 169, 110, 0.08); } -.tz-btn-outline { - background: transparent; - color: var(--tz-acc); - border: 1px solid var(--tz-bdr-g); +/* Element: inline clear button inside input */ +.search-bar__input-clear { + position: absolute; + right: 10px; + background: none; + border: none; + color: var(--color-muted); + cursor: pointer; + font-size: 11px; + padding: 4px; + line-height: 1; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + transition: color var(--transition-fast); } -.tz-btn-outline:not(:disabled):hover { - background: rgba(232, 213, 176, .07); - transform: translateY(-2px); +.search-bar__input-clear:hover { color: var(--color-text); } + +/* Element: image mode row */ +.search-bar__image-row { + flex: 1; + display: flex; + align-items: center; + gap: 12px; + min-width: 0; } -.tz-btn:disabled { - opacity: .35; - cursor: not-allowed; - transform: none !important; - animation: none !important; +.search-bar__thumb-wrap { + flex-shrink: 0; + width: 44px; + height: 44px; + border-radius: var(--radius-sm); + overflow: hidden; + border: 1px solid var(--color-border-gold); } -.tz-btn:active { - transform: translateY(0) !important; +.search-bar__thumb { + width: 100%; + height: 100%; + object-fit: cover; + display: block; } -/* ── Side-by-side image preview ────────────────────────────────────────────── */ -.side-by-side-preview { +.search-bar__image-actions { display: flex; - gap: 24px; - margin-bottom: 24px; + align-items: center; + gap: 8px; flex-wrap: wrap; - animation: tz-fadeup .5s ease both; } -.original-preview, -.cropped-preview { - flex: 1; - min-width: 240px; +/* Element: hidden file input */ +.search-bar__file-input { + position: absolute; + opacity: 0; + pointer-events: none; + width: 0; + height: 0; } -.section-title { - font-family: var(--fm); - font-size: 10px; - font-weight: 500; - letter-spacing: .18em; - text-transform: uppercase; - color: var(--tz-muted); - margin: 0 0 10px; +/* Element: clear-all button (always visible, far right) */ +.search-bar__clear-all { + flex-shrink: 0; + color: var(--color-muted) !important; + border-color: rgba(255, 255, 255, 0.08) !important; } -.original-img-wrap, -.cropped-img-wrap { - position: relative; - border-radius: 14px; - overflow: hidden; +.search-bar__clear-all:hover { + color: var(--color-text) !important; + border-color: rgba(239, 68, 68, 0.3) !important; + background: rgba(239, 68, 68, 0.06) !important; } -.original-img, -.cropped-img { - width: 100%; - max-height: 380px; - object-fit: cover; - display: block; -} -.original-img-actions { - position: absolute; - top: 10px; - right: 10px; -} - -.preview-img-actions { - position: absolute; - bottom: 10px; - right: 10px; -} - -.preview-actions-below { - margin-top: 12px; +/* ══════════════════════════════════════════════════════════════════════════════ + BLOCK: results-section +══════════════════════════════════════════════════════════════════════════════ */ +.results-section { display: flex; - align-items: center; - justify-content: flex-end; - gap: 12px; -} - -/* ── Hidden file input ─────────────────────────────────────────────────────── */ -.file-input-hidden { - position: absolute; - opacity: 0; - pointer-events: none; - width: 0; - height: 0; + flex-direction: column; + gap: 20px; + animation: anim-fade-in 0.3s ease both; } -/* ── Results header ────────────────────────────────────────────────────────── */ -.tz-rh { +/* Element: meta bar (count + filters + inline pagination) */ +.results-section__meta { display: flex; - align-items: flex-start; - justify-content: space-between; + align-items: center; flex-wrap: wrap; - gap: 14px; - margin-bottom: 20px; + gap: 12px; padding-bottom: 16px; - border-bottom: 1px solid var(--tz-bdr); + border-bottom: 1px solid var(--color-border); } -.tz-rm { +.results-section__count { display: flex; align-items: baseline; gap: 10px; } -.tz-rc { +.results-section__count-number { font-size: 28px; font-weight: 800; - letter-spacing: -.03em; - color: var(--tz-txt); + letter-spacing: -0.03em; + color: var(--color-text); } -.tz-rl { - font-family: var(--fm); +.results-section__count-label { + font-family: var(--font-mono); font-size: 11px; - color: var(--tz-muted); - letter-spacing: .12em; + color: var(--color-muted); + letter-spacing: 0.12em; text-transform: uppercase; } -/* ── Result grid ───────────────────────────────────────────────────────────── */ +.results-section__active-filters { + display: flex; + gap: 6px; + flex-wrap: wrap; + align-items: center; + margin-top: 6px; +} + +.results-section__filter-prefix { + font-family: var(--font-mono); + font-size: 10px; + color: var(--color-muted); + letter-spacing: 0.1em; + text-transform: uppercase; +} + +.results-section__filter-badge { + font-family: var(--font-mono); + font-size: 10px; + padding: 3px 9px; + border-radius: 20px; + background: rgba(201, 169, 110, 0.10); + border: 1px solid rgba(201, 169, 110, 0.22); + color: var(--color-accent-2); + letter-spacing: 0.08em; + text-transform: uppercase; +} + +/* Element: right-aligned slot (inline pagination) */ +.results-section__meta-end { + margin-left: auto; +} + +/* Element: filters row (category picker for text search) */ +.results-section__filters { + animation: anim-fade-up 0.3s ease both; +} + + +/* ══════════════════════════════════════════════════════════════════════════════ + BLOCK: result-grid +══════════════════════════════════════════════════════════════════════════════ */ .result-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(225px, 1fr)); gap: 18px; - animation: tz-fadein .4s ease both; + animation: anim-fade-in 0.4s ease both; +} + +/* Modifier: full-page (wider, more columns) */ +.result-grid--full { + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 16px; +} + +@media (min-width: 1400px) { + .result-grid--full { + grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); + } } + +/* ══════════════════════════════════════════════════════════════════════════════ + BLOCK: result-card +══════════════════════════════════════════════════════════════════════════════ */ .result-card { - border-radius: 14px; + border-radius: var(--radius-lg); overflow: hidden; cursor: pointer; - transition: border-color .2s, transform .22s, box-shadow .22s; + background: var(--color-surface); + border: 1px solid var(--color-border); + backdrop-filter: blur(18px); + -webkit-backdrop-filter: blur(18px); + transition: border-color var(--transition-slow), transform var(--transition-slow), box-shadow var(--transition-slow); position: relative; - animation: tz-cardin .45s ease both; + animation: anim-card-in 0.45s ease both; } .result-card:hover { - border-color: var(--tz-bdr-g) !important; + border-color: var(--color-border-gold); transform: translateY(-5px); - box-shadow: 0 20px 48px rgba(0, 0, 0, .55), 0 0 0 1px rgba(201, 169, 110, .15); + box-shadow: 0 20px 48px rgba(0, 0, 0, 0.55), + 0 0 0 1px rgba(201, 169, 110, 0.15); } -.result-thumb { +/* Element: image thumbnail */ +.result-card__thumb { position: relative; aspect-ratio: 4/5; overflow: hidden; } -.result-thumb img { +.result-card__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; - transition: transform .5s ease; + transition: transform 0.5s ease; } -.result-card:hover .result-thumb img { - transform: scale(1.07); -} +.result-card:hover .result-card__thumb img { transform: scale(1.07); } -.result-thumb::after { +.result-card__thumb::after { content: ''; position: absolute; - bottom: 0; - left: 0; - right: 0; + bottom: 0; left: 0; right: 0; height: 55%; - background: linear-gradient(to top, rgba(6, 7, 9, .72) 0%, transparent 100%); + background: linear-gradient(to top, rgba(6, 7, 9, 0.72) 0%, transparent 100%); pointer-events: none; } -.zoom-btn { +/* Element: zoom button */ +.result-card__zoom-btn { position: absolute; top: 10px; right: 10px; width: 32px; height: 32px; - border-radius: 8px; - background: rgba(7, 8, 9, .72); + border-radius: var(--radius-sm); + background: rgba(7, 8, 9, 0.72); backdrop-filter: blur(8px); - border: 1px solid rgba(255, 255, 255, .1); + border: 1px solid rgba(255, 255, 255, 0.1); cursor: pointer; font-size: 12px; display: flex; align-items: center; justify-content: center; opacity: 0; - transform: scale(.8); - transition: opacity .18s, transform .18s; + transform: scale(0.8); + transition: opacity var(--transition-base), transform var(--transition-base); z-index: 2; } -.result-card:hover .zoom-btn { +.result-card:hover .result-card__zoom-btn { opacity: 1; transform: scale(1); } -.result-name { +/* Element: filename label */ +.result-card__name { padding: 11px 13px 8px; - font-family: var(--fm); + font-family: var(--font-mono); font-size: 10px; font-weight: 500; - color: var(--tz-muted); - letter-spacing: .07em; + color: var(--color-muted); + letter-spacing: 0.07em; text-transform: uppercase; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.result-audio { - padding: 0 11px 11px; -} +/* Element: audio player */ +.result-card__audio { padding: 0 11px 11px; } +.result-card__audio audio { width: 100%; height: 28px; border-radius: 6px; } -.result-audio audio { - width: 100%; - height: 28px; - border-radius: 6px; -} -/* ── Pagination ────────────────────────────────────────────────────────────── */ -.pagination-controls { +/* ══════════════════════════════════════════════════════════════════════════════ + BLOCK: pagination +══════════════════════════════════════════════════════════════════════════════ */ +.pagination { display: flex; align-items: center; gap: 12px; - margin-bottom: 20px; } -.pagination-controls button { +/* Modifier: inline (inside meta bar) */ +.pagination--inline { + gap: 8px; +} + +.pagination--inline .pagination__btn { padding: 6px 14px; font-size: 11px; } +.pagination--inline .pagination__label { font-size: 11px; } + +/* Modifier: bottom (centered, with top border) */ +.pagination--bottom { + justify-content: center; + padding-top: 8px; + border-top: 1px solid var(--color-border); +} + +/* Element: button */ +.pagination__btn { padding: 8px 18px; - border-radius: 8px; - font-family: var(--ff); + border-radius: var(--radius-sm); + font-family: var(--font-sans); font-size: 12px; font-weight: 600; - letter-spacing: .06em; + letter-spacing: 0.06em; cursor: pointer; - background: var(--tz-surf); - color: var(--tz-txt); - border: 1px solid var(--tz-bdr2); + background: var(--color-surface); + color: var(--color-text); + border: 1px solid var(--color-border-2); backdrop-filter: blur(10px); - transition: all .15s; + transition: all var(--transition-fast); } -.pagination-controls button:not(:disabled):hover { - border-color: var(--tz-bdr-g); - background: rgba(201, 169, 110, .07); +.pagination__btn:not(:disabled):hover { + border-color: var(--color-border-gold); + background: rgba(201, 169, 110, 0.07); } -.pagination-controls button:disabled { - opacity: .3; - cursor: not-allowed; -} +.pagination__btn:disabled { opacity: 0.3; cursor: not-allowed; } -.pagination-controls span { - font-family: var(--fm); +/* Element: page label */ +.pagination__label { + font-family: var(--font-mono); font-size: 12px; - color: var(--tz-muted); - letter-spacing: .08em; + color: var(--color-muted); + letter-spacing: 0.08em; } -/* ── Misc states ───────────────────────────────────────────────────────────── */ -.search-error { - font-family: var(--fm); - font-size: 13px; - color: #f87171; - padding: 12px 16px; - background: rgba(239, 68, 68, .07); - border: 1px solid rgba(239, 68, 68, .18); - border-radius: 10px; - margin-bottom: 16px; + +/* ══════════════════════════════════════════════════════════════════════════════ + BLOCK: image-preview (post-crop, pre-results) +══════════════════════════════════════════════════════════════════════════════ */ +.image-preview { + display: flex; + gap: 24px; + margin-bottom: 24px; + flex-wrap: wrap; + animation: anim-fade-up 0.5s ease both; } -.empty-hint { - font-family: var(--fm); - font-size: 13px; - color: var(--tz-muted); - text-align: center; - padding: 60px 0; - letter-spacing: .06em; +/* Element: pane (original / cropped) */ +.image-preview__pane { + flex: 1; + min-width: 240px; } -/* ── Lightbox ──────────────────────────────────────────────────────────────── */ -.lb-backdrop { - position: fixed; - inset: 0; - background: rgba(0, 0, 0, .92); - backdrop-filter: blur(22px); - z-index: 1000; +.image-preview__pane-title { + font-family: var(--font-mono); + font-size: 10px; + font-weight: 500; + letter-spacing: 0.18em; + text-transform: uppercase; + color: var(--color-muted); + margin: 0 0 10px; +} + +/* Element: image frame */ +.image-preview__frame { + position: relative; + border-radius: var(--radius-lg); + overflow: hidden; + background: var(--color-surface); + border: 1px solid var(--color-border); + backdrop-filter: blur(18px); + -webkit-backdrop-filter: blur(18px); +} + +.image-preview__img { + width: 100%; + max-height: 380px; + object-fit: cover; + display: block; +} + +/* Element: frame actions (overlaid buttons) */ +.image-preview__frame-actions { + position: absolute; +} + +.image-preview__frame-actions--top { top: 10px; right: 10px; } +.image-preview__frame-actions--bottom { bottom: 10px; right: 10px; } + +/* Element: category options area */ +.image-preview__options { + width: 100%; + flex-basis: 100%; +} + +/* Element: search controls row below previews */ +.image-preview__search-row { display: flex; align-items: center; - justify-content: center; - animation: tz-fadein .2s ease; + justify-content: flex-end; + gap: 12px; + width: 100%; + flex-basis: 100%; + margin-top: 12px; } -.lb-stage { - position: relative; - width: 100vw; - height: 100vh; + +/* ══════════════════════════════════════════════════════════════════════════════ + BLOCK: db-panel (vector database controls) +══════════════════════════════════════════════════════════════════════════════ */ +.db-panel { display: flex; align-items: center; - justify-content: center; - cursor: grab; + gap: 20px; + flex-wrap: wrap; + padding: 13px 20px; + border-radius: var(--radius-lg); + background: var(--color-surface); + border: 1px solid var(--color-border); + backdrop-filter: blur(18px); + -webkit-backdrop-filter: blur(18px); + margin-bottom: 40px; + position: relative; overflow: hidden; } -.lb-stage:active { - cursor: grabbing; +.db-panel::before { + content: ''; + position: absolute; + top: 0; left: 0; right: 0; + height: 1px; + background: linear-gradient(90deg, transparent, var(--color-border-gold), transparent); } -.lb-img { - max-width: 90vw; - max-height: 85vh; - object-fit: contain; - border-radius: 12px; - user-select: none; - box-shadow: 0 40px 80px rgba(0, 0, 0, .8); - transition: none; +/* Element: header */ +.db-panel__header { + display: flex; + align-items: center; + gap: 8px; + flex-shrink: 0; } -.lb-caption { - position: absolute; - bottom: 80px; - left: 50%; - transform: translateX(-50%); - font-family: var(--fm); - font-size: 11px; - letter-spacing: .16em; +.db-panel__pulse { + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--color-green); + animation: anim-pulse 2s infinite; + flex-shrink: 0; +} + +.db-panel__label { + font-family: var(--font-mono); + font-size: 10px; + letter-spacing: 0.15em; text-transform: uppercase; - color: rgba(255, 255, 255, .45); - white-space: nowrap; + color: var(--color-muted); } -.lb-controls { - position: absolute; - bottom: 28px; - left: 50%; - transform: translateX(-50%); +/* Element: actions row */ +.db-panel__actions { display: flex; - gap: 8px; - background: rgba(13, 10, 8, .88); - border: 1px solid var(--tz-bdr2); - border-radius: 12px; - padding: 8px 12px; - backdrop-filter: blur(14px); + gap: 10px; + flex-wrap: wrap; } -.lb-controls button { - width: 34px; - height: 34px; - border-radius: 8px; - background: transparent; - color: var(--tz-txt); - border: 1px solid var(--tz-bdr); - cursor: pointer; - font-size: 14px; +/* Element: action button */ +.db-panel__btn { + display: inline-flex; + align-items: center; + gap: 7px; + padding: 8px 18px; + border-radius: var(--radius-sm); + font-family: var(--font-sans); + font-size: 12px; font-weight: 600; - display: flex; + letter-spacing: 0.04em; + cursor: pointer; + border: 1px solid transparent; + transition: all var(--transition-base); + white-space: nowrap; +} + +.db-panel__btn:disabled { opacity: 0.35; cursor: not-allowed; } +.db-panel__btn:not(:disabled):hover { transform: translateY(-2px); filter: brightness(1.14); } + +/* Modifiers: create / update */ +.db-panel__btn--create { + background: rgba(34, 197, 94, 0.10); + color: #4ade80; + border-color: rgba(34, 197, 94, 0.22); +} + +.db-panel__btn--update { + background: rgba(59, 130, 246, 0.10); + color: #60a5fa; + border-color: rgba(59, 130, 246, 0.22); +} + +.db-panel__btn-icon { font-size: 14px; line-height: 1; } + +/* Element: spinner */ +.db-panel__spinner { + width: 12px; + height: 12px; + border: 2px solid rgba(255, 255, 255, 0.2); + border-top-color: currentColor; + border-radius: 50%; + animation: anim-spin 0.6s linear infinite; + display: inline-block; + flex-shrink: 0; +} + +/* Element: notification */ +.db-panel__notification { + font-family: var(--font-mono); + font-size: 11px; + padding: 7px 12px; + border-radius: 7px; +} + +.db-panel__notification--success { + background: rgba(34, 197, 94, 0.08); + color: #4ade80; + border: 1px solid rgba(34, 197, 94, 0.18); +} + +.db-panel__notification--error { + background: rgba(239, 68, 68, 0.08); + color: #f87171; + border: 1px solid rgba(239, 68, 68, 0.18); +} + + +/* ══════════════════════════════════════════════════════════════════════════════ + BLOCK: settings-panel +══════════════════════════════════════════════════════════════════════════════ */ +.settings-panel { + position: relative; + display: inline-block; +} + +/* Element: toggle button */ +.settings-panel__toggle { + display: inline-flex; align-items: center; - justify-content: center; - transition: background .15s; + gap: 6px; + padding: 13px 22px; + border-radius: var(--radius-md); + font-family: var(--font-sans); + font-size: 13px; + font-weight: 700; + letter-spacing: 0.04em; + cursor: pointer; + background: transparent; + color: var(--color-text); + border: 1px solid var(--color-border-2); + backdrop-filter: blur(10px); + transition: all var(--transition-base); } -.lb-controls button:hover { - background: rgba(255, 255, 255, .06); +.settings-panel__toggle:hover { + border-color: var(--color-border-gold); + transform: translateY(-2px); } -.lb-close { - color: #f87171 !important; - border-color: rgba(239, 68, 68, .3) !important; +.settings-panel__chevron { + font-size: 9px; + opacity: 0.5; + margin-left: 2px; } -.lb-close:hover { - background: rgba(239, 68, 68, .1) !important; +/* Element: dropdown content */ +.settings-panel__content { + position: absolute; + top: calc(100% + 8px); + left: 0; + z-index: 50; + border-radius: var(--radius-lg); + min-width: 340px; + animation: anim-fade-up 0.2s ease both; } -/* ── Crop drawer ───────────────────────────────────────────────────────────── */ + +/* ══════════════════════════════════════════════════════════════════════════════ + BLOCK: crop-drawer +══════════════════════════════════════════════════════════════════════════════ */ .crop-drawer { position: fixed; inset: 0; z-index: 800; - background: rgba(0, 0, 0, .88); + background: rgba(0, 0, 0, 0.88); backdrop-filter: blur(18px); display: flex; align-items: center; justify-content: center; - animation: tz-fadein .2s ease; + animation: anim-fade-in 0.2s ease; } -.crop-drawer-inner { - background: var(--tz-surf2); - border: 1px solid var(--tz-bdr2); - border-radius: 20px; +/* Element: inner modal */ +.crop-drawer__inner { + background: var(--color-surface-2); + border: 1px solid var(--color-border-2); + border-radius: var(--radius-xl); padding: 26px; width: min(680px, 95vw); max-height: 90vh; overflow-y: auto; - box-shadow: 0 40px 80px rgba(0, 0, 0, .6); + box-shadow: 0 40px 80px rgba(0, 0, 0, 0.6); position: relative; display: flex; flex-direction: column; - gap: 0; } -.crop-drawer-inner::before { +.crop-drawer__inner::before { content: ''; position: absolute; - top: 0; - left: 0; - right: 0; + top: 0; left: 0; right: 0; height: 1px; - background: linear-gradient(90deg, transparent, var(--tz-bdr-g), transparent); - border-radius: 20px 20px 0 0; + background: linear-gradient(90deg, transparent, var(--color-border-gold), transparent); + border-radius: var(--radius-xl) var(--radius-xl) 0 0; } -.drawer-title { - font-family: var(--ff); +/* Element: title */ +.crop-drawer__title { + font-family: var(--font-sans); font-size: 19px; font-weight: 800; - letter-spacing: -.02em; - color: var(--tz-txt); + letter-spacing: -0.02em; + color: var(--color-text); margin: 0 0 18px; } -/* Crop stage — key fix: give the stage a defined height so the image is visible */ -.crop-stage { +/* Element: crop stage */ +.crop-drawer__stage { margin-bottom: 18px; display: flex; align-items: flex-start; @@ -1072,20 +1272,18 @@ min-height: 200px; } -.crop-image-wrap { +.crop-drawer__image-wrap { position: relative; display: inline-block; - /* shrink-wrap to image */ line-height: 0; - /* collapse inline gap */ user-select: none; border-radius: 12px; overflow: hidden; - background: var(--tz-surf); + background: var(--color-surface); max-width: 100%; } -.crop-image { +.crop-drawer__image { display: block; max-width: 100%; max-height: 52vh; @@ -1095,32 +1293,162 @@ border-radius: 12px; } -.crop-rect { +/* Element: selection rect */ +.crop-drawer__rect { position: absolute; - border: 2px solid var(--tz-acc); - box-shadow: 0 0 0 9999px rgba(0, 0, 0, .52), 0 0 20px rgba(232, 213, 176, .18); + border: 2px solid var(--color-accent); + box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.52), + 0 0 20px rgba(232, 213, 176, 0.18); cursor: move; touch-action: none; } -.crop-handle { +/* Element: resize handle */ +.crop-drawer__handle { position: absolute; width: 18px; height: 18px; bottom: -2px; right: -2px; - background: var(--tz-acc); + background: var(--color-accent); border-radius: 4px 0 4px 0; cursor: se-resize; } -.drawer-actions { +/* Element: action buttons row */ +.crop-drawer__actions { display: flex; gap: 10px; justify-content: flex-end; } -.db__control { + +/* ══════════════════════════════════════════════════════════════════════════════ + BLOCK: lightbox +══════════════════════════════════════════════════════════════════════════════ */ +.lightbox { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.92); + backdrop-filter: blur(22px); + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + animation: anim-fade-in 0.2s ease; +} + +/* Element: interactive stage */ +.lightbox__stage { position: relative; - top: 1rem; -} \ No newline at end of file + width: 100vw; + height: 100vh; + display: flex; + align-items: center; + justify-content: center; + cursor: grab; + overflow: hidden; +} + +.lightbox__stage:active { cursor: grabbing; } + +/* Element: image */ +.lightbox__image { + max-width: 90vw; + max-height: 85vh; + object-fit: contain; + border-radius: 12px; + user-select: none; + box-shadow: 0 40px 80px rgba(0, 0, 0, 0.8); + transition: none; +} + +/* Element: caption */ +.lightbox__caption { + position: absolute; + bottom: 80px; + left: 50%; + transform: translateX(-50%); + font-family: var(--font-mono); + font-size: 11px; + letter-spacing: 0.16em; + text-transform: uppercase; + color: rgba(255, 255, 255, 0.45); + white-space: nowrap; +} + +/* Element: controls bar */ +.lightbox__controls { + position: absolute; + bottom: 28px; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 8px; + background: rgba(13, 10, 8, 0.88); + border: 1px solid var(--color-border-2); + border-radius: 12px; + padding: 8px 12px; + backdrop-filter: blur(14px); +} + +/* Element: individual control button */ +.lightbox__control-btn { + width: 34px; + height: 34px; + border-radius: var(--radius-sm); + background: transparent; + color: var(--color-text); + border: 1px solid var(--color-border); + cursor: pointer; + font-size: 14px; + font-weight: 600; + display: flex; + align-items: center; + justify-content: center; + transition: background var(--transition-fast); +} + +.lightbox__control-btn:hover { background: rgba(255, 255, 255, 0.06); } + +/* Modifier: close button (red tint) */ +.lightbox__control-btn--close { + color: #f87171 !important; + border-color: rgba(239, 68, 68, 0.3) !important; +} + +.lightbox__control-btn--close:hover { + background: rgba(239, 68, 68, 0.1) !important; +} + + +/* ══════════════════════════════════════════════════════════════════════════════ + RESPONSIVE +══════════════════════════════════════════════════════════════════════════════ */ +@media (max-width: 640px) { + :root { --sticky-bar-height: 60px; } + + .search-bar__inner { padding: 0 14px; gap: 8px; } + .search-bar__image-actions { gap: 6px; } + + .fabric-search__body--results { padding: 16px 14px 60px; } + + .result-grid--full { + grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); + gap: 10px; + } + + .results-section__meta { flex-direction: column; align-items: flex-start; } + .results-section__meta-end { margin-left: 0; } + + /* Hide inline pagination on mobile, bottom one remains */ + .pagination--inline { display: none; } +} + +@media (max-width: 400px) { + .result-grid--full { + grid-template-columns: repeat(2, 1fr); + gap: 8px; + } +} + diff --git a/frontend/src/pages/FabricSearch.tsx b/frontend/src/pages/FabricSearch.tsx index 5b744da..a437468 100644 --- a/frontend/src/pages/FabricSearch.tsx +++ b/frontend/src/pages/FabricSearch.tsx @@ -16,37 +16,33 @@ interface ResultItem { audioSrc?: string; } -interface Pagination { - page: number; - per_page: number; - total_results: number; - total_pages: number; - has_next: boolean; - has_prev: boolean; -} - interface SearchApiResponse { message: string; results: string[]; - pagination: Pagination; + pagination: { + page: number; + per_page: number; + total_results: number; + total_pages: number; + has_next: boolean; + has_prev: boolean; + }; } -// ─── Category definitions ───────────────────────────────────────────────────── +// ─── Constants ──────────────────────────────────────────────────────────────── const CATEGORIES = [ { id: "stock", label: "Stock", icon: "📦" }, { id: "fabric", label: "Fabric", icon: "🧵" }, { id: "design", label: "Design", icon: "🎨" }, { id: "product", label: "Product", icon: "🖼️" }, - // { id: "group", label: "Group", icon: "👥" } ]; -// ─── API helpers ───────────────────────────────────────────────────────────── - const API_BASE = (import.meta.env.VITE_API_URL ?? "") + (import.meta.env.VITE_API_PREFIX ?? ""); - const CDN_BASE = import.meta.env.VITE_AWS_PUBLIC_URL ?? ""; +// ─── Helpers ────────────────────────────────────────────────────────────────── + function toCdnUrl(src: string | undefined): string { if (!src) return ""; if (/^https?:\/\//i.test(src)) return src; @@ -58,13 +54,16 @@ function toResultItem(raw: string): ResultItem { return { imageSrc: raw, filename }; } -// ─── Search hook ───────────────────────────────────────────────────────────── +function cleanName(filename: string): string { + return filename ? filename.split("_")[0].split(".")[0] : ""; +} + +// ─── useSearch hook ─────────────────────────────────────────────────────────── function useSearch() { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [results, setResults] = useState([]); - const runImageSearch = useCallback(async (file: File, category?: string[], limit = 40) => { setLoading(true); @@ -73,15 +72,7 @@ function useSearch() { const form = new FormData(); form.append("file", file); form.append("limit", String(limit)); - console.log("FORM DATA CATEGORY:", category); - console.log("FILE:", file); - console.log("RUN IMAGE CATEGORY VALUE:", category); - console.log("TYPE:", typeof category); - if (category?.length) { - category.forEach((c) => { - form.append("category", c); - }); - } + if (category?.length) category.forEach((c) => { form.append("category", c) }); const res = await fetch(`${API_BASE}/search`, { method: "POST", body: form }); if (!res.ok) { const err = await res.json().catch(() => ({})); @@ -104,11 +95,7 @@ function useSearch() { const form = new FormData(); form.append("search_term", term); form.append("limit", String(limit)); - if (category?.length) { - category.forEach((c) => { - form.append("category", c); - }); - } + if (category?.length) category.forEach((c) => { form.append("category", c) }); const res = await fetch(`${API_BASE}/search`, { method: "POST", body: form }); if (!res.ok) { const err = await res.json().catch(() => ({})); @@ -132,49 +119,55 @@ function useSearch() { return { loading, error, results, runImageSearch, runTextSearch, clear }; } -// ─── DB endpoint ───────────────────────────────────────────────────────────── +// ─── DB endpoint ────────────────────────────────────────────────────────────── async function callDbEndpoint(op: "create" | "update"): Promise { - const url = op === "create" ? `${API_BASE}/database/create/table` : `${API_BASE}/database/update/table`; + const url = + op === "create" + ? `${API_BASE}/database/create/table` + : `${API_BASE}/database/update/table`; const res = await fetch(url, { method: "PUT" }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new Error(data?.detail ?? `Request failed (${res.status})`); return data?.message ?? "Done."; } -// ─── Category Picker ───────────────────────────────────────────────────────── +// ─── CategoryPicker ─────────────────────────────────────────────────────────── interface CategoryPickerProps { selected: string[]; onChange: (cats: string[]) => void; + compact?: boolean; } -function CategoryPicker({ selected, onChange }: CategoryPickerProps) { - const toggle = (id: string) => onChange(selected.includes(id) ? selected.filter((c) => c !== id) : [...selected, id]); +function CategoryPicker({ selected, onChange, compact = false }: CategoryPickerProps) { + const toggle = (id: string) => + onChange(selected.includes(id) ? selected.filter((c) => c !== id) : [...selected, id]); const allOn = selected.length === CATEGORIES.length; const toggleAll = () => onChange(allOn ? [] : CATEGORIES.map((c) => c.id)); return ( -
-
- Filter by Category -
-
+ +
{CATEGORIES.map((cat) => { const active = selected.includes(cat.id); return ( ); })} @@ -183,7 +176,7 @@ function CategoryPicker({ selected, onChange }: CategoryPickerProps) { ); } -// ─── DB Control Panel ───────────────────────────────────────────────────────── +// ─── DbControlPanel ─────────────────────────────────────────────────────────── function DbControlPanel() { const [activeOp, setActiveOp] = useState(null); @@ -205,55 +198,67 @@ function DbControlPanel() { }; return ( -
-
- - Vector Database +
+
+ + Vector Database
-
+ +
+
- {notification &&
{notification.message}
} + + {notification && ( +
+ {notification.message} +
+ )}
); } -// ─── Settings Panel ─────────────────────────────────────────────────────────── +// ─── SettingsPanel ──────────────────────────────────────────────────────────── function SettingsPanel() { const [open, setOpen] = useState(false); return ( -
+
+ {open && ( -
+
)} @@ -261,68 +266,600 @@ function SettingsPanel() { ); } -// ─── Main Search Page ───────────────────────────────────────────────────────── +// ─── StickySearchBar ────────────────────────────────────────────────────────── + +interface StickySearchBarProps { + textQuery: string; + setTextQuery: (v: string) => void; + onSearch: () => void; + onClear: () => void; + loading: boolean; + isImageMode: boolean; + previewUrl: string | null; + onRecrop: () => void; + fileInputId: string; + onFileChange: (e: React.ChangeEvent) => void; +} + +function StickySearchBar({ + textQuery, + setTextQuery, + onSearch, + onClear, + loading, + isImageMode, + previewUrl, + onRecrop, + fileInputId, + onFileChange, + + selectedCategories, + onSetCategories, + searchLimit, + onSetLimit, +}: any) { + return ( +
+
+ + {isImageMode && previewUrl ? ( +
+
+ query +
+ +
+ + + + + +
+
+ ) : ( +
+
+ setTextQuery(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && onSearch()} + /> +
+ + +
+ )} + + +
+ + {/* ✅ NEW: filters always visible */} +
+ + + +
+
+ ); +} +// ─── Pagination ─────────────────────────────────────────────────────────────── + +interface PaginationProps { + page: number; + totalPages: number; + onPrev: () => void; + onNext: () => void; + variant?: "inline" | "bottom"; +} + +function Pagination({ page, totalPages, onPrev, onNext, variant = "bottom" }: PaginationProps) { + return ( +
+ + Page {page} / {totalPages} + +
+ ); +} + +// ─── ResultCard ─────────────────────────────────────────────────────────────── + +interface ResultCardProps { + item: ResultItem; + index: number; + onZoom: (src: string, caption: string) => void; + onBadImage: (src: string) => void; +} + +function ResultCard({ item, index, onZoom, onBadImage }: ResultCardProps) { + const handleClick = () => { + if (!item.imageSrc) return; + onZoom(toCdnUrl(item.imageSrc), cleanName(item.filename)); + }; + + return ( +
+
+ {item.filename} onBadImage(item.imageSrc)} + onClick={handleClick} + /> + +
+ +
+ {cleanName(item.filename)} +
+ + {item.audioSrc && ( +
+
+ )} +
+ ); +} + +// ─── ResultsSection ─────────────────────────────────────────────────────────── + +interface ResultsSectionProps { + results: ResultItem[]; + paginatedResults: ResultItem[]; + page: number; + totalPages: number; + selectedCategories: string[]; + isTextSearch: boolean; + onSetCategories: (cats: string[]) => void; + onPrev: () => void; + onNext: () => void; + onZoom: (src: string, caption: string) => void; + onBadImage: (src: string) => void; +} + +function ResultsSection({ + results, + paginatedResults, + page, + totalPages, + selectedCategories, + onSetCategories, + onPrev, + onNext, + onZoom, + onBadImage, +}: any) { + return ( +
+ +
+
{results.length} results
+ +
+ + {/* ✅ ALWAYS visible */} +
+ +
+ +
+ {paginatedResults.map((item: any, idx: number) => ( + + ))} +
+
+ ); +} + +// ─── LimitSlider ────────────────────────────────────────────────────────────── + +interface LimitSliderProps { + value: number; + onChange: (v: number) => void; + label?: string; +} + +function LimitSlider({ value, onChange, label = "Results" }: LimitSliderProps) { + return ( +
+ {label} +
+
+ onChange(Number(e.target.value))} + /> +
+ {value} +
+ ); +} + +// ─── Lightbox ───────────────────────────────────────────────────────────────── + +interface LightboxProps { + src: string; + caption: string | null; + scale: number; + offset: { x: number; y: number }; + onClose: () => void; + onWheel: React.WheelEventHandler; + onMouseDown: React.MouseEventHandler; + onMouseMove: React.MouseEventHandler; + onMouseUp: React.MouseEventHandler; + onZoomIn: () => void; + onZoomOut: () => void; + onReset: () => void; +} + +function Lightbox({ + src, caption, scale, offset, + onClose, onWheel, onMouseDown, onMouseMove, onMouseUp, + onZoomIn, onZoomOut, onReset, +}: LightboxProps) { + return ( +
{ + if ((e.target as HTMLElement).classList.contains("lightbox")) onClose(); + }} + > +
+ {caption + + {caption &&
{caption}
} + +
+ + + + +
+
+
+ ); +} + +// ─── CropDrawer ─────────────────────────────────────────────────────────────── + +interface CropDrawerProps { + rawImageUrl: string; + cropRect: { x: number; y: number; w: number; h: number }; + imgRef: React.RefObject; + onImageLoad: () => void; + onDragStart: (e: React.MouseEvent) => void; + onResizeStart: (e: React.MouseEvent) => void; + onConfirm: () => void; + onCancel: () => void; +} + +function CropDrawer({ + rawImageUrl, cropRect, imgRef, + onImageLoad, onDragStart, onResizeStart, + onConfirm, onCancel, +}: CropDrawerProps) { + return ( +
+
+

Crop & Confirm

+ +
+
+ Select crop area +
{ e.preventDefault(); onDragStart(e); }} + > +
{ e.stopPropagation(); onResizeStart(e); }} + /> +
+
+
+ +
+ + +
+
+
+ ); +} + +// ─── ImagePreview (pre-results, post-crop) ──────────────────────────────────── + +interface ImagePreviewProps { + originalUrl: string; + croppedUrl: string | null; + searchLimit: number; + selectedCategories: string[]; + loading: boolean; + onClear: () => void; + onRecrop: () => void; + onSetCategories: (cats: string[]) => void; + onSetLimit: (v: number) => void; + onSearch: () => void; +} + +function ImagePreview({ + originalUrl, croppedUrl, searchLimit, + selectedCategories, loading, + onClear, onRecrop, onSetCategories, onSetLimit, onSearch, +}: ImagePreviewProps) { + return ( +
+ {/* Original */} +
+

Original Image

+
+ original +
+ +
+
+ +
+
+
+ + {/* Cropped */} + {croppedUrl && ( + <> +
+

Cropped Image

+
+ cropped +
+
+ +
+ +
+ +
+ + +
+ + )} +
+ ); +} + +// ─── Hero ───────────────────────────────────────────────────────────────────── + +interface HeroProps { + textQuery: string; + setTextQuery: (v: string) => void; + onTextSearch: () => void; + onFileChange: (e: React.ChangeEvent) => void; + fileInputId: string; + searchLimit: number; + onSetLimit: (v: number) => void; + selectedCategories: string[]; + onSetCategories: (cats: string[]) => void; + loading: boolean; +} + +function Hero({ + textQuery, setTextQuery, onTextSearch, + onFileChange, fileInputId, + searchLimit, onSetLimit, + selectedCategories, onSetCategories, + loading, +}: HeroProps) { + return ( +
+
+
Fabric Intelligence
+

+ Find the clothing +
+ you couldn't find. +

+

Visual & semantic search — powered by vectors

+
+ +
+ {/* Text search */} +
+ setTextQuery(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && onTextSearch()} + /> + +
+ + {/* Limit */} + + + {/* Divider */} +
or
+ + {/* Image upload */} + + +
+ + {/* Category filter */} +
+ +
+
+ ); +} + +// ─── Main Page ──────────────────────────────────────────────────────────────── export default function Search() { const { loading, error, results, runImageSearch, runTextSearch, clear } = useSearch(); const [file, setFile] = useState(null); const [textQuery, setTextQuery] = useState(""); - const [previewUrlOriginal, setPreviewUrlOriginal] = useState(null); + const [previewUrlOrig, setPreviewUrlOrig] = useState(null); const [notification, setNotification] = useState(null); const [selectingImage, setSelectingImage] = useState(false); - - // User-defined search settings const [searchLimit, setSearchLimit] = useState(40); const [selectedCategories, setSelectedCategories] = useState([]); - - // Track whether the last search was a text search (to show category picker in results) const [isTextSearch, setIsTextSearch] = useState(false); - - // Derive category param — array or undefined (= no filter) - const categoryParam = selectedCategories.length > 0 ? selectedCategories : undefined; - - const pageSize = 4; const [page, setPage] = useState(1); - const [badImages, setBadImages] = useState>(new Set()); - const markBadImage = (src?: string) => { - if (!src) return; - setBadImages((prev) => new Set([...prev, src])); - }; - const originalFileRef = useRef(null); - - // Crop / drawer state + // Crop / drawer const [drawerOpen, setDrawerOpen] = useState(false); const [cropRect, setCropRect] = useState({ x: 20, y: 20, w: 160, h: 160 }); - const imgRef = useRef(null); const [rawImageUrl, setRawImageUrl] = useState(null); const [croppedPreviewUrl, setCroppedPreviewUrl] = useState(null); - + const imgRef = useRef(null); const draggingRef = useRef(false); const resizingRef = useRef(false); const lastMouseRef = useRef({ x: 0, y: 0 }); + const originalFileRef = useRef(null); - // ── URL helpers ──────────────────────────────────────────────────────────── + // Lightbox + const [lightboxOpen, setLightboxOpen] = useState(false); + const [activeSrc, setActiveSrc] = useState(null); + const [activeCaption, setActiveCaption] = useState(null); + const [lbScale, setLbScale] = useState(1); + const [lbOffset, setLbOffset] = useState({ x: 0, y: 0 }); + const draggingLbRef = useRef(false); + const lastLbPosRef = useRef({ x: 0, y: 0 }); + const MIN_SCALE = 0.5, MAX_SCALE = 6, ZOOM_STEP = 0.2; - const setOriginalObjectUrl = useCallback( - (f: File | null) => { - if (previewUrlOriginal) { - try { - URL.revokeObjectURL(previewUrlOriginal); - } catch {} - } - if (f) { - try { - setPreviewUrlOriginal(URL.createObjectURL(f)); - } catch { - setPreviewUrlOriginal(null); - } - } else setPreviewUrlOriginal(null); - }, - [previewUrlOriginal] - ); + // Misc + const [previewUrl, setPreviewUrl] = useState(null); + const heroFileId = useId(); + const stickyFileId = useId(); + const categoryParam = selectedCategories.length > 0 ? selectedCategories : undefined; + const PAGE_SIZE = 12; + + // ── Object URL helpers ───────────────────────────────────────────────────── + + const setOriginalObjectUrl = useCallback((f: File | null) => { + if (previewUrlOrig) { try { URL.revokeObjectURL(previewUrlOrig); } catch { } } + setPreviewUrlOrig(f ? (() => { try { return URL.createObjectURL(f); } catch { return null; } })() : null); + }, [previewUrlOrig]); const dataUrlToFile = useCallback(async (dataUrl: string, filename = "query.png"): Promise => { const res = await fetch(dataUrl); @@ -332,9 +869,9 @@ export default function Search() { const urlToFile = useCallback(async (url: string, filename = "query.jpg"): Promise => { const res = await fetch(url, { credentials: "omit" }); - if (!res.ok) throw new Error(`Failed to fetch image_url: ${res.status}`); + if (!res.ok) throw new Error(`Failed to fetch image: ${res.status}`); const blob = await res.blob(); - const ext = (blob.type && blob.type.split("/")[1]) || "jpg"; + const ext = (blob.type?.split("/")[1]) || "jpg"; const name = filename.endsWith(`.${ext}`) ? filename : `${filename}.${ext}`; return new File([blob], name, { type: blob.type || "image/jpeg" }); }, []); @@ -343,7 +880,6 @@ export default function Search() { const onFileChange = (e: React.ChangeEvent) => { const f = e.target.files?.[0] ?? null; - // Reset input so the same file can be re-selected e.target.value = ""; if (!f) return; setSelectingImage(true); @@ -355,22 +891,22 @@ export default function Search() { setCropRect({ x: 20, y: 20, w: 160, h: 160 }); setNotification(null); setBadImages(new Set()); - // Clear any previous cropped state setCroppedPreviewUrl(null); setFile(null); setIsTextSearch(false); - try { - window.dispatchEvent(new CustomEvent("fabricai:clear-pending-action")); - } catch {} + try { window.dispatchEvent(new CustomEvent("fabricai:clear-pending-action")); } catch { } }; - // ── Auto-run from URL / localStorage ────────────────────────────────────── + // ── Auto-run ─────────────────────────────────────────────────────────────── const didAutoRun = useRef(false); useEffect(() => { if (didAutoRun.current) return; const params = new URLSearchParams(window.location.search); const urlImage = params.get("image_url"); + + const afterRun = () => { try { localStorage.removeItem("mcp_last_search"); } catch { } setPage(1); }; + if (urlImage) { didAutoRun.current = true; (async () => { @@ -381,17 +917,12 @@ export default function Search() { setFile(f); setIsTextSearch(false); await runImageSearch(f, selectedCategories, searchLimit); - } catch { - setNotification({ message: "Could not auto-run search from URL.", type: "error" }); - } finally { - try { - localStorage.removeItem("mcp_last_search"); - } catch {} - setPage(1); - } + } catch { setNotification({ message: "Could not auto-run search from URL.", type: "error" }); } + finally { afterRun(); } })(); return; } + try { const raw = localStorage.getItem("mcp_last_search"); if (!raw) return; @@ -408,43 +939,38 @@ export default function Search() { setFile(f); setIsTextSearch(false); await runImageSearch(f, selectedCategories, searchLimit); - } catch { - setNotification({ message: "Could not auto-run search payload.", type: "error" }); - } finally { - try { - localStorage.removeItem("mcp_last_search"); - } catch {} - setPage(1); - } + } catch { setNotification({ message: "Could not auto-run search payload.", type: "error" }); } + finally { afterRun(); } })(); - } catch {} - }, [runImageSearch, dataUrlToFile, urlToFile, setOriginalObjectUrl, searchLimit]); + } catch { } + }, [runImageSearch, dataUrlToFile, urlToFile, setOriginalObjectUrl, searchLimit, selectedCategories]); // ── Search handlers ──────────────────────────────────────────────────────── - const cleanName = (filename: string) => (filename ? filename.split("_")[0].split(".")[0] : ""); - - const handleSearch = async () => { + const handleImageSearch = async () => { if (!file) return; setNotification(null); setIsTextSearch(false); - try { - await runImageSearch(file, categoryParam, searchLimit); - setPage(1); - } catch { - setNotification({ message: "Search failed.", type: "error" }); - } + try { await runImageSearch(file, categoryParam, searchLimit); setPage(1); } + catch { setNotification({ message: "Search failed.", type: "error" }); } }; const handleTextSearch = async () => { if (!textQuery.trim()) return; setNotification(null); setIsTextSearch(true); - try { - await runTextSearch(textQuery.trim(), categoryParam, searchLimit); - setPage(1); - } catch { - setNotification({ message: "Search failed.", type: "error" }); + try { await runTextSearch(textQuery.trim(), categoryParam, searchLimit); setPage(1); } + catch { setNotification({ message: "Search failed.", type: "error" }); } + }; + + const handleCategoryChange = async (cats: string[]) => { + setSelectedCategories(cats); + setPage(1); + + if (file && !isTextSearch) { + await runImageSearch(file, cats, searchLimit); + } else if (textQuery.trim()) { + await runTextSearch(textQuery.trim(), cats, searchLimit); } }; @@ -456,26 +982,14 @@ export default function Search() { setNotification(null); setBadImages(new Set()); setIsTextSearch(false); - if (rawImageUrl) { - try { - URL.revokeObjectURL(rawImageUrl); - } catch {} - setRawImageUrl(null); - } - if (previewUrlOriginal) { - try { - URL.revokeObjectURL(previewUrlOriginal); - } catch {} - setPreviewUrlOriginal(null); - } - try { - window.dispatchEvent(new CustomEvent("fabricai:clear-pending-action")); - } catch {} + if (rawImageUrl) { try { URL.revokeObjectURL(rawImageUrl); } catch { } setRawImageUrl(null); } + if (previewUrlOrig) { try { URL.revokeObjectURL(previewUrlOrig); } catch { } setPreviewUrlOrig(null); } + try { window.dispatchEvent(new CustomEvent("fabricai:clear-pending-action")); } catch { } originalFileRef.current = null; setCroppedPreviewUrl(null); }; - // ── Results ──────────────────────────────────────────────────────────────── + // ── Results helpers ──────────────────────────────────────────────────────── const visibleResults = useMemo( () => results.filter((item) => !item.imageSrc || !badImages.has(item.imageSrc)), @@ -483,45 +997,26 @@ export default function Search() { ); const paginatedResults = useMemo(() => { - const start = (page - 1) * pageSize; - return visibleResults.slice(start, start + pageSize); + const start = (page - 1) * PAGE_SIZE; + return visibleResults.slice(start, start + PAGE_SIZE); }, [page, visibleResults]); - const totalPages = Math.max(1, Math.ceil(visibleResults.length / pageSize)); - const fileid = useId(); + const totalPages = Math.max(1, Math.ceil(visibleResults.length / PAGE_SIZE)); + + const safePrev = useMemo(() => throttle(() => setPage((p) => Math.max(1, p - 1)), 1000), []); + const safeNext = useMemo(() => throttle(() => setPage((p) => Math.min(totalPages, p + 1)), 1000), [totalPages]); + + // ── Preview URL for cropped file ─────────────────────────────────────────── - // Preview URL for the file (separate from originalFileRef url) - const [previewUrl, setPreviewUrl] = useState(null); useEffect(() => { let cur: string | null = null; - if (file) { - try { - cur = URL.createObjectURL(file); - setPreviewUrl(cur); - } catch { - setPreviewUrl(null); - } - } else { - setPreviewUrl(null); - } - return () => { - if (cur) { - try { - URL.revokeObjectURL(cur); - } catch {} - } - }; + if (file) { try { cur = URL.createObjectURL(file); setPreviewUrl(cur); } catch { setPreviewUrl(null); } } + else setPreviewUrl(null); + return () => { if (cur) { try { URL.revokeObjectURL(cur); } catch { } } }; }, [file]); - useEffect(() => { - return () => { - if (previewUrlOriginal) { - try { - URL.revokeObjectURL(previewUrlOriginal); - } catch {} - } - }; - }, [previewUrlOriginal]); + useEffect(() => () => { if (previewUrlOrig) { try { URL.revokeObjectURL(previewUrlOrig); } catch { } } }, [previewUrlOrig]); + useEffect(() => () => { if (rawImageUrl) { try { URL.revokeObjectURL(rawImageUrl); } catch { } } }, [rawImageUrl]); useEffect(() => { const wrapper = document.querySelector(".app-wrapper"); @@ -531,25 +1026,15 @@ export default function Search() { // ── Lightbox ─────────────────────────────────────────────────────────────── - const [lightboxOpen, setLightboxOpen] = useState(false); - const [activeSrc, setActiveSrc] = useState(null); - const [activeCaption, setActiveCaption] = useState(null); - const [scale, setScale] = useState(1); - const [offset, setOffset] = useState({ x: 0, y: 0 }); - const draggingLightboxRef = useRef(false); - const lastPosRef = useRef({ x: 0, y: 0 }); - const MIN_SCALE = 0.5, - MAX_SCALE = 6, - ZOOM_STEP = 0.2; - const openLightbox = (src: string, caption?: string) => { setActiveSrc(src); setActiveCaption(caption ?? null); - setScale(1); - setOffset({ x: 0, y: 0 }); + setLbScale(1); + setLbOffset({ x: 0, y: 0 }); setLightboxOpen(true); document.body.style.overflow = "hidden"; }; + const closeLightbox = () => { setLightboxOpen(false); setActiveSrc(null); @@ -559,27 +1044,20 @@ export default function Search() { const onLbWheel: React.WheelEventHandler = (e) => { e.preventDefault(); - setScale((s) => Math.min(MAX_SCALE, Math.max(MIN_SCALE, s + (e.deltaY > 0 ? -ZOOM_STEP : ZOOM_STEP)))); + setLbScale((s) => Math.min(MAX_SCALE, Math.max(MIN_SCALE, s + (e.deltaY > 0 ? -ZOOM_STEP : ZOOM_STEP)))); }; const onLbMouseDown: React.MouseEventHandler = (e) => { - draggingLightboxRef.current = true; - lastPosRef.current = { x: e.clientX, y: e.clientY }; + draggingLbRef.current = true; + lastLbPosRef.current = { x: e.clientX, y: e.clientY }; }; const onLbMouseMove: React.MouseEventHandler = (e) => { - if (!draggingLightboxRef.current) return; - const dx = e.clientX - lastPosRef.current.x, - dy = e.clientY - lastPosRef.current.y; - lastPosRef.current = { x: e.clientX, y: e.clientY }; - setOffset((o) => ({ x: o.x + dx, y: o.y + dy })); + if (!draggingLbRef.current) return; + const dx = e.clientX - lastLbPosRef.current.x; + const dy = e.clientY - lastLbPosRef.current.y; + lastLbPosRef.current = { x: e.clientX, y: e.clientY }; + setLbOffset((o) => ({ x: o.x + dx, y: o.y + dy })); }; - const onLbMouseUpOrLeave = () => { - draggingLightboxRef.current = false; - }; - - // ── Pagination throttle ──────────────────────────────────────────────────── - - const safePrev = useMemo(() => throttle(() => setPage((p) => Math.max(1, p - 1)), 1000), []); - const safeNext = useMemo(() => throttle(() => setPage((p) => Math.min(totalPages, p + 1)), 1000), [totalPages]); + const onLbMouseUp = () => { draggingLbRef.current = false; }; // ── Crop mouse events ────────────────────────────────────────────────────── @@ -591,49 +1069,32 @@ export default function Search() { const dy = ev.clientY - lastMouseRef.current.y; lastMouseRef.current = { x: ev.clientX, y: ev.clientY }; setCropRect((prev) => { - if (draggingRef.current) { - return { x: Math.max(0, prev.x + dx), y: Math.max(0, prev.y + dy), w: prev.w, h: prev.h }; - } - if (resizingRef.current) { - return { x: prev.x, y: prev.y, w: Math.max(40, prev.w + dx), h: Math.max(40, prev.h + dy) }; - } + if (draggingRef.current) return { x: Math.max(0, prev.x + dx), y: Math.max(0, prev.y + dy), w: prev.w, h: prev.h }; + if (resizingRef.current) return { x: prev.x, y: prev.y, w: Math.max(40, prev.w + dx), h: Math.max(40, prev.h + dy) }; return prev; }); }; - const onUp = () => { - draggingRef.current = false; - resizingRef.current = false; - }; + const onUp = () => { draggingRef.current = false; resizingRef.current = false; }; window.addEventListener("mousemove", onMove); window.addEventListener("mouseup", onUp); - return () => { - window.removeEventListener("mousemove", onMove); - window.removeEventListener("mouseup", onUp); - }; + return () => { window.removeEventListener("mousemove", onMove); window.removeEventListener("mouseup", onUp); }; }, []); - // Called once the image inside the crop drawer loads — centre the crop rect const onCropImageLoad = () => { const img = imgRef.current; if (!img) return; - const dispW = img.clientWidth, - dispH = img.clientHeight; + const dispW = img.clientWidth, dispH = img.clientHeight; const short = Math.min(dispW, dispH); const size = Math.round(short * 0.55); - const x = Math.round((dispW - size) / 2); - const y = Math.round((dispH - size) / 2); - setCropRect({ x, y, w: size, h: size }); + setCropRect({ x: Math.round((dispW - size) / 2), y: Math.round((dispH - size) / 2), w: size, h: size }); setSelectingImage(false); }; - // Confirm crop → create cropped file + auto-run search const makeCroppedPreview = async (): Promise => { if (!rawImageUrl || !imgRef.current) return; const imgEl = imgRef.current; - const dispW = imgEl.clientWidth, - dispH = imgEl.clientHeight; - const natW = imgEl.naturalWidth, - natH = imgEl.naturalHeight; + const dispW = imgEl.clientWidth, dispH = imgEl.clientHeight; + const natW = imgEl.naturalWidth, natH = imgEl.naturalHeight; const sx = Math.round((cropRect.x / dispW) * natW); const sy = Math.round((cropRect.y / dispH) * natH); const sw = Math.max(1, Math.round((cropRect.w / dispW) * natW)); @@ -643,466 +1104,182 @@ export default function Search() { canvas.width = sw; canvas.height = sh; const ctx = canvas.getContext("2d"); - if (!ctx) { - setNotification({ message: "Could not crop image.", type: "error" }); - return; - } + if (!ctx) { setNotification({ message: "Could not crop image.", type: "error" }); return; } const imgObj = new Image(); imgObj.crossOrigin = "anonymous"; imgObj.src = rawImageUrl; - try { - await new Promise((res, rej) => { - imgObj.onload = () => res(); - imgObj.onerror = () => rej(); - }); - } catch { - setNotification({ message: "Could not load image for cropping.", type: "error" }); - return; - } + try { await new Promise((res, rej) => { imgObj.onload = () => res(); imgObj.onerror = () => rej(); }); } + catch { setNotification({ message: "Could not load image for cropping.", type: "error" }); return; } ctx.drawImage(imgObj, sx, sy, sw, sh, 0, 0, sw, sh); const blob = await new Promise((resolve) => canvas.toBlob(resolve, "image/jpeg", 0.92)); - if (!blob) { - setNotification({ message: "Could not generate cropped image.", type: "error" }); - return; - } + if (!blob) { setNotification({ message: "Could not generate cropped image.", type: "error" }); return; } const croppedFile = new File([blob], `query-cropped-${Date.now()}.jpg`, { type: "image/jpeg" }); - - // Clean up raw url - try { - URL.revokeObjectURL(rawImageUrl); - } catch {} + try { URL.revokeObjectURL(rawImageUrl); } catch { } setRawImageUrl(null); - - // Store cropped file + preview URL setFile(croppedFile); setCroppedPreviewUrl(URL.createObjectURL(croppedFile)); setDrawerOpen(false); setPage(1); setBadImages(new Set()); setIsTextSearch(false); - - // Auto-run image search immediately after crop setNotification(null); - try { - await runImageSearch(croppedFile, selectedCategories, searchLimit); - } catch { - setNotification({ message: "Search failed.", type: "error" }); - } + // try { await runImageSearch(croppedFile, selectedCategories, searchLimit); } + // catch { setNotification({ message: "Search failed.", type: "error" }); } }; const cancelCropAndClose = () => { - if (rawImageUrl) { - try { - URL.revokeObjectURL(rawImageUrl); - } catch {} - setRawImageUrl(null); - } + if (rawImageUrl) { try { URL.revokeObjectURL(rawImageUrl); } catch { } setRawImageUrl(null); } setDrawerOpen(false); setCropRect({ x: 20, y: 20, w: 160, h: 160 }); setSelectingImage(false); }; - // Cleanup rawImageUrl on unmount - useEffect( - () => () => { - if (rawImageUrl) { - try { - URL.revokeObjectURL(rawImageUrl); - } catch {} - } - }, - [rawImageUrl] - ); + const openRecrop = () => { + const orig = originalFileRef.current; + if (!orig) { setNotification({ message: "Original image not available.", type: "error" }); return; } + if (rawImageUrl) { try { URL.revokeObjectURL(rawImageUrl); } catch { } } + setRawImageUrl(URL.createObjectURL(orig)); + setDrawerOpen(true); + setCropRect({ x: 20, y: 20, w: 160, h: 160 }); + }; - // ── Render ───────────────────────────────────────────────────────────────── + // ── Derived ──────────────────────────────────────────────────────────────── - const showHero = !file && !drawerOpen && visibleResults.length === 0 && !loading; + const hasResults = visibleResults.length > 0; + const showHero = !file && !drawerOpen && !hasResults && !loading && !isTextSearch; + const showStickyBar = hasResults || (loading && (!!file || isTextSearch)); + const stickyPreview = croppedPreviewUrl || previewUrlOrig || previewUrl; + + // ── Render ───────────────────────────────────────────────────────────────── return ( -
- {/* Animated canvas background */} - -
- - {/* ── Settings Panel (replaces inline DB Control Panel) ── */} -
- -
+
+ + {/* Sticky search bar */} + {showStickyBar && ( + + )} - {/* ── Hero / Search entry ── */} - {showHero && ( -
-
-
Fabric Intelligence
-

- Find the clothing -
- you couldn't find. -

-

Visual & semantic search — powered by vectors

-
- -
- {/* Text search row */} -
- setTextQuery(e.target.value)} - onKeyDown={(e) => e.key === "Enter" && handleTextSearch()} - /> - -
- - {/* Result limit */} -
- Results -
-
- setSearchLimit(Number(e.target.value))} - /> -
- {searchLimit} -
- -
or
- - {/* Image search trigger */} - - -
+ {/* Page body */} +
- {/* Category filter */} -
- + {/* Header + settings — hidden while results are shown */} + {!showStickyBar && ( +
+ +
+
)} - {/* ── Image preview after crop confirmed ── */} - {file && !drawerOpen && ( -
- {/* Original */} -
-

Original Image

-
- original -
- {/* ── Clear Search button ── */} - -
-
- -
-
-
- {/* Cropped preview + controls */} - {croppedPreviewUrl && ( - <> -
-

Cropped Image

-
- cropped -
-
- -
- -
- {/* Limit + re-search button */} -
-
- Limit -
-
- setSearchLimit(Number(e.target.value))} - /> -
- {searchLimit} -
- -
- - )} -
+ {/* Hero */} + {showHero && ( + + )} + + {/* Image preview (post-crop, pre-results) */} + {file && !drawerOpen && !hasResults && !loading && ( + )} - {/* ── Notifications / Loading / Errors ── */} + {/* Notifications / states */} {notification && } {(loading || selectingImage) && } - {error &&

{error}

} - - {/* ── Results ── */} - {visibleResults.length > 0 && ( - <> -
-
-
- {visibleResults.length} - matches found -
- {selectedCategories.length > 0 && ( -
- in - {selectedCategories.map((c) => ( - - {CATEGORIES.find((cat) => cat.id === c)?.icon} {c} - - ))} -
- )} -
- - {/* Refine search bar (text mode) */} - {!file && ( -
-
- setTextQuery(e.target.value)} - onKeyDown={(e) => e.key === "Enter" && handleTextSearch()} - /> - - -
-
- )} -
- - {/* ── Category picker visible after text search ── */} - {isTextSearch && ( -
- -
- )} - -
- - - Page {page} / {totalPages} - - -
- -
- {paginatedResults.map((item, idx) => ( -
-
- {item.filename} markBadImage(item.imageSrc)} - onClick={() => { - if (!item.imageSrc) return; - openLightbox(toCdnUrl(item.imageSrc), cleanName(item.filename)); - }} - /> - -
-
{ - if (!item.imageSrc) return; - openLightbox(toCdnUrl(item.imageSrc), cleanName(item.filename)); - }} - > - {cleanName(item.filename)} -
- {item.audioSrc && ( -
-
- )} -
- ))} -
- + {error &&

{error}

} + + {/* Results */} + {hasResults && ( + setBadImages((prev) => new Set([...prev, src]))} + /> )} - {!loading && file && visibleResults.length === 0 &&

— no matches found —

} + {!loading && file && visibleResults.length === 0 && ( +

— no matches found —

+ )}
- {/* ── Lightbox ── */} + {/* Lightbox */} {lightboxOpen && activeSrc && ( -
{ - if ((e.target as HTMLElement).classList.contains("lb-backdrop")) closeLightbox(); - }} - > -
- {activeCaption - {activeCaption &&
{activeCaption}
} -
- - - - -
-
-
+ setLbScale((s) => Math.min(MAX_SCALE, s + ZOOM_STEP))} + onZoomOut={() => setLbScale((s) => Math.max(MIN_SCALE, s - ZOOM_STEP))} + onReset={() => { setLbScale(1); setLbOffset({ x: 0, y: 0 }); }} + /> )} - {/* ── Crop Drawer ── */} - {drawerOpen && rawImageUrl && ( -
-
-

Crop & Confirm

- -
-
- Select crop area -
{ - e.preventDefault(); - draggingRef.current = true; - lastMouseRef.current = { x: e.clientX, y: e.clientY }; - }} - > -
{ - e.stopPropagation(); - resizingRef.current = true; - lastMouseRef.current = { x: e.clientX, y: e.clientY }; - }} - /> -
-
-
- -
- - -
-
-
+ {/* Crop drawer */} + {drawerOpen && rawImageUrl && imgRef !== null && ( + { draggingRef.current = true; lastMouseRef.current = { x: e.clientX, y: e.clientY }; }} + onResizeStart={(e) => { resizingRef.current = true; lastMouseRef.current = { x: e.clientX, y: e.clientY }; }} + onConfirm={makeCroppedPreview} + onCancel={cancelCropAndClose} + /> )}
); From dcf6b0724fb383ed57bf1e5e42986e0814aa37f3 Mon Sep 17 00:00:00 2001 From: RecursiveZero Date: Mon, 6 Apr 2026 19:26:47 +0530 Subject: [PATCH 11/12] [TZF-260026]: update front end --- backend/Dockerfile | 21 ++++++++++++++++ docker-compose.yml | 25 +++++++++++++++++++ frontend/Dockerfile | 17 +++++++++++++ frontend/src/Routing.tsx | 2 +- .../{adhaar.tsx => AadhaarCardReader.tsx} | 2 -- frontend/src/pages/AudioForm.tsx | 3 ++- frontend/src/pages/ComingSoon.tsx | 1 + frontend/src/pages/Contact.tsx | 1 + frontend/src/pages/FabricList.tsx | 1 + frontend/src/pages/FabricSearch.tsx | 11 +++++--- frontend/src/pages/ImageDescriptor.tsx | 1 + .../{CardReader.tsx => PanCardReader.tsx} | 6 ++--- 12 files changed, 81 insertions(+), 10 deletions(-) create mode 100644 backend/Dockerfile create mode 100644 docker-compose.yml create mode 100644 frontend/Dockerfile rename frontend/src/pages/{adhaar.tsx => AadhaarCardReader.tsx} (99%) rename frontend/src/pages/{CardReader.tsx => PanCardReader.tsx} (99%) diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..d0c006b --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,21 @@ +FROM python:3.11-slim +WORKDIR /app + +# System deps +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential curl && \ + rm -rf /var/lib/apt/lists/* + +# Poetry setup +RUN pip install poetry && \ + poetry config virtualenvs.create false + +# Step 1: Heavy Dependencies (Cached) +COPY pyproject.toml poetry.lock* LICENSE-PYTHON README.md ./ +RUN poetry install --all-extras --with dev --no-interaction --no-ansi --no-root + +# Step 2: Your Code +COPY . . + +# Step 3: Fast metadata install (to enable 'fabric' command) +RUN poetry install --all-extras --with dev --no-interaction --no-ansi \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..74126f5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +services: + api: + build: ./backend + container_name: tz_backend + volumes: + - ./backend:/app + - /app/.venv + ports: + - "8002:8002" # Opens 8002 on your Ubuntu machine + command: poetry run fabric dev + restart: unless-stopped + + web: + build: ./frontend + container_name: tz_frontend + volumes: + - ./frontend:/app + - /app/node_modules + ports: + - "5173:5173" # Opens 5173 on your Ubuntu machine + # Note: Ensure your 'npm run dev' uses --port 5173 + command: npm run dev -- --host 0.0.0.0 --port 5173 + depends_on: + - api + restart: unless-stopped \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..e1b16d5 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,17 @@ +FROM node:22-slim + +WORKDIR /app + +# 1. Install dependencies (Cached layer) +# Using 'ci' instead of 'install' is a best practice for production/Docker +# as it ensures a clean install based strictly on your package-lock.json +COPY package*.json ./ +RUN npm install + +# 2. Copy the rest of your code +COPY . . + +# 3. Inform Docker which port the app uses +EXPOSE 5173 + +# Note: We do not put a CMD here because we handle it in docker-compose.yml \ No newline at end of file diff --git a/frontend/src/Routing.tsx b/frontend/src/Routing.tsx index c489c53..762e76f 100644 --- a/frontend/src/Routing.tsx +++ b/frontend/src/Routing.tsx @@ -11,7 +11,7 @@ import Search from "./pages/FabricSearch"; import Home from "./pages/Home"; import ImageDescription from "./pages/ImageDescriptor"; import Reader from "./pages/Reader"; -import AadhaarCardReader from "./pages/adhaar"; +import AadhaarCardReader from "./pages/Aadhaar"; export const Routing = () => { usePageTracking(); diff --git a/frontend/src/pages/adhaar.tsx b/frontend/src/pages/AadhaarCardReader.tsx similarity index 99% rename from frontend/src/pages/adhaar.tsx rename to frontend/src/pages/AadhaarCardReader.tsx index 0f1f468..602a780 100644 --- a/frontend/src/pages/adhaar.tsx +++ b/frontend/src/pages/AadhaarCardReader.tsx @@ -1,5 +1,3 @@ -"use client"; - import { useState, useCallback } from "react"; import Cropper from "react-easy-crop"; diff --git a/frontend/src/pages/AudioForm.tsx b/frontend/src/pages/AudioForm.tsx index 4b073b3..bafb7c9 100644 --- a/frontend/src/pages/AudioForm.tsx +++ b/frontend/src/pages/AudioForm.tsx @@ -1,10 +1,11 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { useLocation, useNavigate } from "react-router-dom"; + import Loader from "../components/Loader"; import Notification from "../components/Notification"; import { useUploadAndRecord } from "../hooks/useUploadAndRecord"; -import "@/assets/styles/UploadPage.css"; import { generateFabricName } from "../utils/fabric-name"; +import "@/assets/styles/UploadPage.css"; type AudioMode = "upload" | "record"; diff --git a/frontend/src/pages/ComingSoon.tsx b/frontend/src/pages/ComingSoon.tsx index c7aa928..2f78102 100644 --- a/frontend/src/pages/ComingSoon.tsx +++ b/frontend/src/pages/ComingSoon.tsx @@ -1,4 +1,5 @@ import type React from "react"; + import "@/assets/styles/ComingSoon.css"; const ComingSoon: React.FC = () => { diff --git a/frontend/src/pages/Contact.tsx b/frontend/src/pages/Contact.tsx index 12a05a5..b4f7d4f 100644 --- a/frontend/src/pages/Contact.tsx +++ b/frontend/src/pages/Contact.tsx @@ -1,4 +1,5 @@ import { useState } from "react"; + import "@/assets/styles/contact.css"; import { FULL_API_URL } from '@/constants'; diff --git a/frontend/src/pages/FabricList.tsx b/frontend/src/pages/FabricList.tsx index bc5f286..7f804ee 100644 --- a/frontend/src/pages/FabricList.tsx +++ b/frontend/src/pages/FabricList.tsx @@ -1,5 +1,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { FiZoomIn } from "react-icons/fi"; + import { BASE_URL } from "../constants"; import { fetchContent, type MediaItem } from "../services/content_api"; import "@/assets/styles/ContentGrid.css"; diff --git a/frontend/src/pages/FabricSearch.tsx b/frontend/src/pages/FabricSearch.tsx index a437468..73c98e7 100644 --- a/frontend/src/pages/FabricSearch.tsx +++ b/frontend/src/pages/FabricSearch.tsx @@ -1,9 +1,10 @@ -import "@/assets/styles/FabricSearch.css"; import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react"; + import FabricSearchHeader from "../components/FabricSearchHeader"; import Loader from "../components/Loader"; import Notification from "../components/Notification"; import { throttle } from "../utils/throttle"; +import "@/assets/styles/FabricSearch.css"; // ─── Types ─────────────────────────────────────────────────────────────────── @@ -279,6 +280,10 @@ interface StickySearchBarProps { onRecrop: () => void; fileInputId: string; onFileChange: (e: React.ChangeEvent) => void; + selectedCategories: string[]; + onSetCategories: (cats: string[]) => void; + searchLimit: number; + onSetLimit: (v: number) => void; } function StickySearchBar({ @@ -297,7 +302,7 @@ function StickySearchBar({ onSetCategories, searchLimit, onSetLimit, -}: any) { +}: StickySearchBarProps) { return (
@@ -476,7 +481,7 @@ function ResultsSection({ onNext, onZoom, onBadImage, -}: any) { +}: ResultsSectionProps) { return (
diff --git a/frontend/src/pages/ImageDescriptor.tsx b/frontend/src/pages/ImageDescriptor.tsx index c403a58..a4cbdb2 100644 --- a/frontend/src/pages/ImageDescriptor.tsx +++ b/frontend/src/pages/ImageDescriptor.tsx @@ -1,4 +1,5 @@ import { useEffect, useRef, useState } from "react"; + import DescriptionBox from "../components/DescriptionBox"; import DrawerToggle from "../components/DrawerToggle"; import Header from "../components/ImageDescriptorHeader"; diff --git a/frontend/src/pages/CardReader.tsx b/frontend/src/pages/PanCardReader.tsx similarity index 99% rename from frontend/src/pages/CardReader.tsx rename to frontend/src/pages/PanCardReader.tsx index e456993..7eda533 100644 --- a/frontend/src/pages/CardReader.tsx +++ b/frontend/src/pages/PanCardReader.tsx @@ -6,7 +6,6 @@ import * as htmlToImage from "html-to-image"; import { FULL_API_URL } from "@/constants"; - type PanResult = { type: string; name: string; @@ -16,7 +15,8 @@ type PanResult = { }; type Point = { x: number; y: number }; type Area = { x: number; y: number; width: number; height: number }; -const CardReader = () => { + +const PanCardReader = () => { const navigate = useNavigate(); const [hovered, setHovered] = useState(false); @@ -1126,4 +1126,4 @@ const styles: any = { }; -export default CardReader; \ No newline at end of file +export default PanCardReader; \ No newline at end of file From f5d0ef4b9d388e967b76ae12594333a71003425a Mon Sep 17 00:00:00 2001 From: RecursiveZero Date: Mon, 6 Apr 2026 20:47:17 +0530 Subject: [PATCH 12/12] [TZF-260026]: Added Apply filter to stop immediate search on select of category --- frontend/src/Routing.tsx | 4 +-- frontend/src/assets/styles/FabricSearch.css | 5 +++ frontend/src/pages/FabricSearch.tsx | 35 +++++++++++++++------ 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/frontend/src/Routing.tsx b/frontend/src/Routing.tsx index 762e76f..cb69c02 100644 --- a/frontend/src/Routing.tsx +++ b/frontend/src/Routing.tsx @@ -2,7 +2,7 @@ import { Route, Routes } from "react-router-dom"; import { NotFound } from "./components/NotFound"; import { usePageTracking } from "./hooks/usePageTracking"; import UploadPage from "./pages/AudioForm"; -import CardReader from "./pages/CardReader"; +import CardReader from "./pages/PanCardReader"; import ComingSoon from "./pages/ComingSoon"; import { ContactUs } from "./pages/Contact"; import Chat from "./pages/FabricChat"; @@ -11,7 +11,7 @@ import Search from "./pages/FabricSearch"; import Home from "./pages/Home"; import ImageDescription from "./pages/ImageDescriptor"; import Reader from "./pages/Reader"; -import AadhaarCardReader from "./pages/Aadhaar"; +import AadhaarCardReader from "./pages/AadhaarCardReader"; export const Routing = () => { usePageTracking(); diff --git a/frontend/src/assets/styles/FabricSearch.css b/frontend/src/assets/styles/FabricSearch.css index d9afb19..77b37e1 100644 --- a/frontend/src/assets/styles/FabricSearch.css +++ b/frontend/src/assets/styles/FabricSearch.css @@ -720,6 +720,7 @@ flex-direction: column; gap: 20px; animation: anim-fade-in 0.3s ease both; + margin-top:1.5rem; } /* Element: meta bar (count + filters + inline pagination) */ @@ -730,6 +731,10 @@ gap: 12px; padding-bottom: 16px; border-bottom: 1px solid var(--color-border); + div { + color: #3d4c46; + font-size: 1.5rem; + } } .results-section__count { diff --git a/frontend/src/pages/FabricSearch.tsx b/frontend/src/pages/FabricSearch.tsx index 73c98e7..f35d255 100644 --- a/frontend/src/pages/FabricSearch.tsx +++ b/frontend/src/pages/FabricSearch.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react"; +import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react"; import FabricSearchHeader from "../components/FabricSearchHeader"; import Loader from "../components/Loader"; @@ -142,10 +142,21 @@ interface CategoryPickerProps { } function CategoryPicker({ selected, onChange, compact = false }: CategoryPickerProps) { - const toggle = (id: string) => - onChange(selected.includes(id) ? selected.filter((c) => c !== id) : [...selected, id]); - const allOn = selected.length === CATEGORIES.length; - const toggleAll = () => onChange(allOn ? [] : CATEGORIES.map((c) => c.id)); + const [tempSelected, setTempSelected] = useState(selected); + const toggle = (id: string) => { + setTempSelected(tempSelected.includes(id) ? tempSelected.filter((c) => c !== id) : [...tempSelected, id]); + } + const allOn = tempSelected.length === CATEGORIES.length; + const toggleAll = () => {setTempSelected(allOn ? [] : CATEGORIES.map((c) => c.id))}; + + const applySearch = () => { + console.log("Apply search with categories:", tempSelected); + onChange(tempSelected); + }; + + useEffect(() => { + setTempSelected(selected); + }, [selected]); return (
@@ -158,7 +169,7 @@ function CategoryPicker({ selected, onChange, compact = false }: CategoryPickerP
{CATEGORIES.map((cat) => { - const active = selected.includes(cat.id); + const active = tempSelected.includes(cat.id); return ( + )}
); @@ -475,8 +491,6 @@ function ResultsSection({ paginatedResults, page, totalPages, - selectedCategories, - onSetCategories, onPrev, onNext, onZoom, @@ -491,9 +505,9 @@ function ResultsSection({
{/* ✅ ALWAYS visible */} -
+ {/*
-
+
*/}
{paginatedResults.map((item: any, idx: number) => ( @@ -1246,6 +1260,7 @@ export default function Search() { onPrev={safePrev} onNext={safeNext} onZoom={openLightbox} + isTextSearch={isTextSearch} onBadImage={(src) => setBadImages((prev) => new Set([...prev, src]))} /> )}