Add discovery search history with auto-save, fix timeout causing search failure

- Save every search result to DB for later review
- Add '搜索历史' tab with timeline view, load/delete records
- Raise discovery search timeout from 30s to 120s (Bing Puppeteer needs ~40s)
- Reduce search queries from 4 to 3 for faster response
- New model: DiscoveryRecord (user_id, product, market, companies JSON)
- API: POST/GET/DELETE /api/v1/discovery/records
- Migration: discovery_records table
This commit is contained in:
TradeMate Dev
2026-05-27 15:54:50 +08:00
parent 6f0d8b0fb4
commit c1638db6b2
8 changed files with 308 additions and 10 deletions
+131
View File
@@ -0,0 +1,131 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, desc
from typing import List, Optional
from app.database import get_db
from app.api.v1.deps import get_current_user_id
from app.models.discovery_record import DiscoveryRecord
from pydantic import BaseModel
from datetime import datetime
import uuid
router = APIRouter()
class SaveRecordRequest(BaseModel):
product: str
market: str = ""
companies: list
class RecordResponse(BaseModel):
id: str
product: str
market: str
companies: list
created_at: str
@router.post("/records")
async def save_record(
req: SaveRecordRequest,
user_id: str = Depends(get_current_user_id),
db: AsyncSession = Depends(get_db),
):
record = DiscoveryRecord(
user_id=uuid.UUID(user_id),
product=req.product,
market=req.market,
companies=req.companies,
)
db.add(record)
await db.commit()
return {"success": True, "data": {"id": str(record.id)}}
@router.get("/records")
async def list_records(
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=50),
user_id: str = Depends(get_current_user_id),
db: AsyncSession = Depends(get_db),
):
stmt = (
select(DiscoveryRecord)
.where(DiscoveryRecord.user_id == uuid.UUID(user_id))
.order_by(desc(DiscoveryRecord.created_at))
.offset((page - 1) * size)
.limit(size)
)
result = await db.execute(stmt)
records = result.scalars().all()
total_stmt = select(DiscoveryRecord).where(DiscoveryRecord.user_id == uuid.UUID(user_id))
total_result = await db.execute(total_stmt)
total = len(total_result.scalars().all())
return {
"items": [
{
"id": str(r.id),
"product": r.product,
"market": r.market,
"companies": r.companies or [],
"created_at": r.created_at.isoformat() if r.created_at else "",
}
for r in records
],
"total": total,
"page": page,
"size": size,
}
@router.get("/records/{record_id}")
async def get_record(
record_id: str,
user_id: str = Depends(get_current_user_id),
db: AsyncSession = Depends(get_db),
):
try:
rid = uuid.UUID(record_id)
except ValueError:
raise HTTPException(status_code=400, detail="Invalid record ID")
result = await db.execute(
select(DiscoveryRecord).where(
DiscoveryRecord.id == rid,
DiscoveryRecord.user_id == uuid.UUID(user_id),
)
)
r = result.scalar_one_or_none()
if not r:
raise HTTPException(status_code=404, detail="Record not found")
return {
"id": str(r.id),
"product": r.product,
"market": r.market,
"companies": r.companies or [],
"created_at": r.created_at.isoformat() if r.created_at else "",
}
@router.delete("/records/{record_id}")
async def delete_record(
record_id: str,
user_id: str = Depends(get_current_user_id),
db: AsyncSession = Depends(get_db),
):
try:
rid = uuid.UUID(record_id)
except ValueError:
raise HTTPException(status_code=400, detail="Invalid record ID")
result = await db.execute(
select(DiscoveryRecord).where(
DiscoveryRecord.id == rid,
DiscoveryRecord.user_id == uuid.UUID(user_id),
)
)
r = result.scalar_one_or_none()
if not r:
raise HTTPException(status_code=404, detail="Record not found")
await db.delete(r)
await db.commit()
return {"success": True}
+2 -1
View File
@@ -54,7 +54,7 @@ async def health():
return {"status": "ok", "app": settings.APP_NAME, "version": "1.0.0"}
from app.api.v1 import auth, marketing, translate, customer, quotation, whatsapp, product, exchange, push, admin, analytics, teams, onboarding, notification, feedback, payment, interaction, silent_pattern, training, followup, ai_assistant, discovery, certification, invoice, usage, referral, admin_search, search
from app.api.v1 import auth, marketing, translate, customer, quotation, whatsapp, product, exchange, push, admin, analytics, teams, onboarding, notification, feedback, payment, interaction, silent_pattern, training, followup, ai_assistant, discovery, discovery_record, certification, invoice, usage, referral, admin_search, search
app.include_router(auth.router, prefix="/api/v1/auth", tags=["auth"])
app.include_router(marketing.router, prefix="/api/v1/marketing", tags=["marketing"])
@@ -79,6 +79,7 @@ app.include_router(training.router, prefix="/api/v1/training", tags=["training"]
app.include_router(followup.router, prefix="/api/v1/followup", tags=["followup"])
app.include_router(ai_assistant.router, prefix="/api/v1/ai", tags=["ai-assistant"])
app.include_router(discovery.router, prefix="/api/v1/discovery", tags=["discovery"])
app.include_router(discovery_record.router, prefix="/api/v1/discovery", tags=["discovery"])
app.include_router(certification.router, prefix="/api/v1/certification", tags=["certification"])
app.include_router(invoice.router, prefix="/api/v1/invoices", tags=["invoices"])
app.include_router(usage.router, prefix="/api/v1/usage", tags=["usage"])
+2
View File
@@ -16,6 +16,7 @@ from .certification import Certification, CertType, CertStatus
from .invoice import Invoice, InvoiceType, InvoiceStatus
from .referral import ReferralCode, Referral
from .search_provider import SearchProvider
from .discovery_record import DiscoveryRecord
__all__ = [
"User", "Product",
@@ -36,4 +37,5 @@ __all__ = [
"Invoice", "InvoiceType", "InvoiceStatus",
"ReferralCode", "Referral",
"SearchProvider",
"DiscoveryRecord",
]
+16
View File
@@ -0,0 +1,16 @@
from sqlalchemy import Column, String, DateTime, Text
from sqlalchemy.dialects.postgresql import UUID, JSONB
from datetime import datetime
from app.database import Base
import uuid
class DiscoveryRecord(Base):
__tablename__ = "discovery_records"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
user_id = Column(UUID(as_uuid=True), nullable=False, index=True)
product = Column(String(500), nullable=False)
market = Column(String(200), default="")
companies = Column(JSONB, default=[])
created_at = Column(DateTime, default=datetime.utcnow)
+1 -1
View File
@@ -125,7 +125,7 @@ URL: {company_url}
async def _web_search_all(self, queries: list) -> dict:
try:
results = await search_bing_batch(queries[:4], max_per_query=5)
results = await search_bing_batch(queries[:3], max_per_query=4)
if results:
return {"results": self._dedup_and_filter(results)[:15], "provider": "bing"}
except Exception as e: