Spaces:
Sleeping
Sleeping
Seth
commited on
Commit
·
bc701fb
1
Parent(s):
b667e4a
update
Browse files- backend/app/main.py +161 -16
- frontend/src/pages/Repository.jsx +58 -5
backend/app/main.py
CHANGED
|
@@ -197,8 +197,61 @@ async def upload_asset(
|
|
| 197 |
user_id=1 # Default user - in production, get from session/auth
|
| 198 |
)
|
| 199 |
db.add(db_asset)
|
| 200 |
-
|
| 201 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
|
| 203 |
return {
|
| 204 |
"id": db_asset.id,
|
|
@@ -207,7 +260,7 @@ async def upload_asset(
|
|
| 207 |
"product_category": db_asset.product_category,
|
| 208 |
"sub_category": db_asset.sub_category,
|
| 209 |
"size": db_asset.size,
|
| 210 |
-
"created_at": db_asset.created_at.isoformat()
|
| 211 |
}
|
| 212 |
except Exception as db_error:
|
| 213 |
# If database save fails, still return success (file is saved)
|
|
@@ -226,20 +279,112 @@ async def upload_asset(
|
|
| 226 |
raise HTTPException(status_code=500, detail=str(e))
|
| 227 |
|
| 228 |
@app.get("/api/assets", response_model=List[AssetResponse])
|
| 229 |
-
async def get_assets(
|
|
|
|
|
|
|
|
|
|
| 230 |
"""Get list of assets"""
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
"
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
|
| 244 |
# ---- Post Management ----
|
| 245 |
|
|
|
|
| 197 |
user_id=1 # Default user - in production, get from session/auth
|
| 198 |
)
|
| 199 |
db.add(db_asset)
|
| 200 |
+
try:
|
| 201 |
+
db.commit()
|
| 202 |
+
try:
|
| 203 |
+
db.refresh(db_asset)
|
| 204 |
+
except Exception as refresh_error:
|
| 205 |
+
# Refresh might fail due to version string, but commit succeeded
|
| 206 |
+
# Query the asset back to get the ID
|
| 207 |
+
if "Could not determine version" in str(refresh_error):
|
| 208 |
+
from sqlalchemy import text
|
| 209 |
+
result = db.execute(
|
| 210 |
+
text("SELECT id, created_at FROM assets WHERE name = :name AND file_path = :file_path ORDER BY id DESC LIMIT 1"),
|
| 211 |
+
{"name": file.filename, "file_path": str(file_path)}
|
| 212 |
+
)
|
| 213 |
+
row = result.fetchone()
|
| 214 |
+
if row:
|
| 215 |
+
db_asset.id = row[0]
|
| 216 |
+
# Set created_at if available
|
| 217 |
+
if hasattr(db_asset, 'created_at') and row[1]:
|
| 218 |
+
db_asset.created_at = row[1]
|
| 219 |
+
else:
|
| 220 |
+
raise refresh_error
|
| 221 |
+
except Exception as commit_error:
|
| 222 |
+
# If commit fails due to version string issue, try raw SQL
|
| 223 |
+
db.rollback()
|
| 224 |
+
error_str = str(commit_error)
|
| 225 |
+
if "Could not determine version" in error_str:
|
| 226 |
+
# Try using raw SQL to insert
|
| 227 |
+
from sqlalchemy import text
|
| 228 |
+
try:
|
| 229 |
+
result = db.execute(
|
| 230 |
+
text("""
|
| 231 |
+
INSERT INTO assets (name, file_path, file_type, product_category, sub_category, size, user_id, created_at)
|
| 232 |
+
VALUES (:name, :file_path, :file_type, :product_category, :sub_category, :size, :user_id, NOW())
|
| 233 |
+
RETURNING id, created_at
|
| 234 |
+
"""),
|
| 235 |
+
{
|
| 236 |
+
"name": file.filename,
|
| 237 |
+
"file_path": str(file_path),
|
| 238 |
+
"file_type": file_type,
|
| 239 |
+
"product_category": product_category or "ocr",
|
| 240 |
+
"sub_category": sub_category if sub_category and sub_category != "none" else None,
|
| 241 |
+
"size": file_size,
|
| 242 |
+
"user_id": 1
|
| 243 |
+
}
|
| 244 |
+
)
|
| 245 |
+
row = result.fetchone()
|
| 246 |
+
if row:
|
| 247 |
+
db_asset.id = row[0]
|
| 248 |
+
db_asset.created_at = row[1]
|
| 249 |
+
db.commit()
|
| 250 |
+
except Exception as sql_error:
|
| 251 |
+
print(f"Raw SQL insert also failed: {sql_error}")
|
| 252 |
+
raise commit_error
|
| 253 |
+
else:
|
| 254 |
+
raise commit_error
|
| 255 |
|
| 256 |
return {
|
| 257 |
"id": db_asset.id,
|
|
|
|
| 260 |
"product_category": db_asset.product_category,
|
| 261 |
"sub_category": db_asset.sub_category,
|
| 262 |
"size": db_asset.size,
|
| 263 |
+
"created_at": db_asset.created_at.isoformat() if hasattr(db_asset, 'created_at') else datetime.utcnow().isoformat()
|
| 264 |
}
|
| 265 |
except Exception as db_error:
|
| 266 |
# If database save fails, still return success (file is saved)
|
|
|
|
| 279 |
raise HTTPException(status_code=500, detail=str(e))
|
| 280 |
|
| 281 |
@app.get("/api/assets", response_model=List[AssetResponse])
|
| 282 |
+
async def get_assets(
|
| 283 |
+
product_category: Optional[str] = None,
|
| 284 |
+
db: Session = Depends(get_db)
|
| 285 |
+
):
|
| 286 |
"""Get list of assets"""
|
| 287 |
+
try:
|
| 288 |
+
from app.models import Asset
|
| 289 |
+
from sqlalchemy import text
|
| 290 |
+
|
| 291 |
+
# Try using ORM first
|
| 292 |
+
try:
|
| 293 |
+
query = db.query(Asset)
|
| 294 |
+
|
| 295 |
+
if product_category and product_category != "all":
|
| 296 |
+
query = query.filter(Asset.product_category == product_category)
|
| 297 |
+
|
| 298 |
+
db_assets = query.order_by(Asset.created_at.desc()).all()
|
| 299 |
+
|
| 300 |
+
# Convert to response format
|
| 301 |
+
assets = []
|
| 302 |
+
for asset in db_assets:
|
| 303 |
+
assets.append({
|
| 304 |
+
"id": asset.id,
|
| 305 |
+
"name": asset.name,
|
| 306 |
+
"file_type": asset.file_type,
|
| 307 |
+
"product_category": asset.product_category,
|
| 308 |
+
"sub_category": asset.sub_category,
|
| 309 |
+
"size": asset.size,
|
| 310 |
+
"created_at": asset.created_at
|
| 311 |
+
})
|
| 312 |
+
except Exception as orm_error:
|
| 313 |
+
# If ORM fails due to version string issue, use raw SQL
|
| 314 |
+
print(f"ORM query warning, using raw SQL: {orm_error}")
|
| 315 |
+
try:
|
| 316 |
+
sql_query = "SELECT id, name, file_path, file_type, product_category, sub_category, size, created_at FROM assets"
|
| 317 |
+
params = {}
|
| 318 |
+
if product_category and product_category != "all":
|
| 319 |
+
sql_query += " WHERE product_category = :product_category"
|
| 320 |
+
params["product_category"] = product_category
|
| 321 |
+
sql_query += " ORDER BY created_at DESC"
|
| 322 |
+
|
| 323 |
+
result = db.execute(text(sql_query), params)
|
| 324 |
+
rows = result.fetchall()
|
| 325 |
+
|
| 326 |
+
assets = []
|
| 327 |
+
for row in rows:
|
| 328 |
+
assets.append({
|
| 329 |
+
"id": row[0],
|
| 330 |
+
"name": row[1],
|
| 331 |
+
"file_type": row[3],
|
| 332 |
+
"product_category": row[4],
|
| 333 |
+
"sub_category": row[5],
|
| 334 |
+
"size": row[6],
|
| 335 |
+
"created_at": row[7]
|
| 336 |
+
})
|
| 337 |
+
except Exception as sql_error:
|
| 338 |
+
print(f"Raw SQL query also failed: {sql_error}")
|
| 339 |
+
assets = []
|
| 340 |
+
|
| 341 |
+
# Merge with mock data (as requested - keep dummy content)
|
| 342 |
+
mock_assets = [
|
| 343 |
+
{
|
| 344 |
+
"id": 9991,
|
| 345 |
+
"name": "OCR_Demo_Screenshot.png",
|
| 346 |
+
"file_type": "image",
|
| 347 |
+
"product_category": "ocr",
|
| 348 |
+
"sub_category": None,
|
| 349 |
+
"size": 2516582,
|
| 350 |
+
"created_at": datetime(2024, 12, 20)
|
| 351 |
+
},
|
| 352 |
+
{
|
| 353 |
+
"id": 9992,
|
| 354 |
+
"name": "P2P_Workflow_Diagram.pdf",
|
| 355 |
+
"file_type": "document",
|
| 356 |
+
"product_category": "p2p",
|
| 357 |
+
"sub_category": "Budget Approval Workflow",
|
| 358 |
+
"size": 1024000,
|
| 359 |
+
"created_at": datetime(2024, 12, 19)
|
| 360 |
+
},
|
| 361 |
+
{
|
| 362 |
+
"id": 9993,
|
| 363 |
+
"name": "O2C_Process_Video.mp4",
|
| 364 |
+
"file_type": "video",
|
| 365 |
+
"product_category": "o2c",
|
| 366 |
+
"sub_category": "Sales Order Workflow",
|
| 367 |
+
"size": 15728640,
|
| 368 |
+
"created_at": datetime(2024, 12, 18)
|
| 369 |
+
}
|
| 370 |
+
]
|
| 371 |
+
|
| 372 |
+
# Combine real assets with mock assets (real assets first)
|
| 373 |
+
return assets + mock_assets
|
| 374 |
+
except Exception as e:
|
| 375 |
+
# If database query fails, return mock data only
|
| 376 |
+
print(f"Database query warning: {e}")
|
| 377 |
+
return [
|
| 378 |
+
{
|
| 379 |
+
"id": 1,
|
| 380 |
+
"name": "OCR_Demo_Screenshot.png",
|
| 381 |
+
"file_type": "image",
|
| 382 |
+
"product_category": "ocr",
|
| 383 |
+
"sub_category": None,
|
| 384 |
+
"size": 2516582,
|
| 385 |
+
"created_at": datetime.utcnow()
|
| 386 |
+
}
|
| 387 |
+
]
|
| 388 |
|
| 389 |
# ---- Post Management ----
|
| 390 |
|
frontend/src/pages/Repository.jsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import React, { useState, useRef } from 'react';
|
| 2 |
import { motion, AnimatePresence } from 'framer-motion';
|
| 3 |
import {
|
| 4 |
Upload,
|
|
@@ -104,6 +104,8 @@ export default function Repository() {
|
|
| 104 |
const [uploadProductCategory, setUploadProductCategory] = useState('');
|
| 105 |
const [uploadSubCategory, setUploadSubCategory] = useState('');
|
| 106 |
const [isUploading, setIsUploading] = useState(false);
|
|
|
|
|
|
|
| 107 |
const fileInputRef = useRef(null);
|
| 108 |
|
| 109 |
const toggleProduct = (productId) => {
|
|
@@ -114,14 +116,14 @@ export default function Repository() {
|
|
| 114 |
);
|
| 115 |
};
|
| 116 |
|
| 117 |
-
const filteredAssets =
|
| 118 |
const matchesSearch = asset.name.toLowerCase().includes(searchQuery.toLowerCase());
|
| 119 |
const matchesProduct = selectedProduct === 'all' || asset.product === selectedProduct;
|
| 120 |
return matchesSearch && matchesProduct;
|
| 121 |
});
|
| 122 |
|
| 123 |
const getAssetsByProduct = (productId) => {
|
| 124 |
-
return
|
| 125 |
};
|
| 126 |
|
| 127 |
const getTypeIcon = (type) => {
|
|
@@ -138,6 +140,57 @@ export default function Repository() {
|
|
| 138 |
return product?.color || 'slate';
|
| 139 |
};
|
| 140 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
const handleFileSelect = (files) => {
|
| 142 |
const fileArray = Array.from(files);
|
| 143 |
setSelectedFiles(fileArray);
|
|
@@ -202,8 +255,8 @@ export default function Repository() {
|
|
| 202 |
// Show success message
|
| 203 |
alert(`Successfully uploaded ${selectedFiles.length} file(s)!`);
|
| 204 |
|
| 205 |
-
//
|
| 206 |
-
|
| 207 |
} catch (error) {
|
| 208 |
console.error('Upload error:', error);
|
| 209 |
alert(`Upload failed: ${error.message}`);
|
|
|
|
| 1 |
+
import React, { useState, useRef, useEffect } from 'react';
|
| 2 |
import { motion, AnimatePresence } from 'framer-motion';
|
| 3 |
import {
|
| 4 |
Upload,
|
|
|
|
| 104 |
const [uploadProductCategory, setUploadProductCategory] = useState('');
|
| 105 |
const [uploadSubCategory, setUploadSubCategory] = useState('');
|
| 106 |
const [isUploading, setIsUploading] = useState(false);
|
| 107 |
+
const [assets, setAssets] = useState(mockAssets);
|
| 108 |
+
const [isLoadingAssets, setIsLoadingAssets] = useState(false);
|
| 109 |
const fileInputRef = useRef(null);
|
| 110 |
|
| 111 |
const toggleProduct = (productId) => {
|
|
|
|
| 116 |
);
|
| 117 |
};
|
| 118 |
|
| 119 |
+
const filteredAssets = assets.filter(asset => {
|
| 120 |
const matchesSearch = asset.name.toLowerCase().includes(searchQuery.toLowerCase());
|
| 121 |
const matchesProduct = selectedProduct === 'all' || asset.product === selectedProduct;
|
| 122 |
return matchesSearch && matchesProduct;
|
| 123 |
});
|
| 124 |
|
| 125 |
const getAssetsByProduct = (productId) => {
|
| 126 |
+
return assets.filter(asset => asset.product === productId);
|
| 127 |
};
|
| 128 |
|
| 129 |
const getTypeIcon = (type) => {
|
|
|
|
| 140 |
return product?.color || 'slate';
|
| 141 |
};
|
| 142 |
|
| 143 |
+
// Fetch assets from API
|
| 144 |
+
const fetchAssets = async () => {
|
| 145 |
+
setIsLoadingAssets(true);
|
| 146 |
+
try {
|
| 147 |
+
const response = await fetch('/api/assets');
|
| 148 |
+
if (response.ok) {
|
| 149 |
+
const data = await response.json();
|
| 150 |
+
// Convert API response to match mockAssets format
|
| 151 |
+
const formattedAssets = data.map(asset => ({
|
| 152 |
+
id: asset.id,
|
| 153 |
+
name: asset.name,
|
| 154 |
+
type: asset.file_type,
|
| 155 |
+
product: asset.product_category,
|
| 156 |
+
subCategory: asset.sub_category,
|
| 157 |
+
size: formatFileSize(asset.size),
|
| 158 |
+
date: asset.created_at ? new Date(asset.created_at).toISOString().split('T')[0] : new Date().toISOString().split('T')[0]
|
| 159 |
+
}));
|
| 160 |
+
setAssets(formattedAssets);
|
| 161 |
+
} else {
|
| 162 |
+
console.error('Failed to fetch assets');
|
| 163 |
+
// Keep mockAssets on error
|
| 164 |
+
}
|
| 165 |
+
} catch (error) {
|
| 166 |
+
console.error('Error fetching assets:', error);
|
| 167 |
+
// Keep mockAssets on error
|
| 168 |
+
} finally {
|
| 169 |
+
setIsLoadingAssets(false);
|
| 170 |
+
}
|
| 171 |
+
};
|
| 172 |
+
|
| 173 |
+
// Format file size
|
| 174 |
+
const formatFileSize = (bytes) => {
|
| 175 |
+
if (!bytes) return '0 B';
|
| 176 |
+
const k = 1024;
|
| 177 |
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
| 178 |
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
| 179 |
+
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
| 180 |
+
};
|
| 181 |
+
|
| 182 |
+
// Fetch assets on component mount and when upload dialog closes
|
| 183 |
+
useEffect(() => {
|
| 184 |
+
fetchAssets();
|
| 185 |
+
}, []);
|
| 186 |
+
|
| 187 |
+
// Refresh assets after successful upload
|
| 188 |
+
useEffect(() => {
|
| 189 |
+
if (!uploadDialogOpen && !isUploading) {
|
| 190 |
+
fetchAssets();
|
| 191 |
+
}
|
| 192 |
+
}, [uploadDialogOpen, isUploading]);
|
| 193 |
+
|
| 194 |
const handleFileSelect = (files) => {
|
| 195 |
const fileArray = Array.from(files);
|
| 196 |
setSelectedFiles(fileArray);
|
|
|
|
| 255 |
// Show success message
|
| 256 |
alert(`Successfully uploaded ${selectedFiles.length} file(s)!`);
|
| 257 |
|
| 258 |
+
// Refresh assets list to show newly uploaded files
|
| 259 |
+
await fetchAssets();
|
| 260 |
} catch (error) {
|
| 261 |
console.error('Upload error:', error);
|
| 262 |
alert(`Upload failed: ${error.message}`);
|