diff --git a/backend/.coverage b/backend/.coverage
new file mode 100644
index 0000000..0b7ab1a
Binary files /dev/null and b/backend/.coverage differ
diff --git a/backend/app/ai/providers/claude.py b/backend/app/ai/providers/claude.py
index 18a795e..d9a78c5 100644
--- a/backend/app/ai/providers/claude.py
+++ b/backend/app/ai/providers/claude.py
@@ -1,6 +1,5 @@
from typing import Dict, Any, Optional
import json
-from anthropic import AsyncAnthropic
from app.ai.base import AIProvider
@@ -19,6 +18,13 @@ SYSTEM_PROMPTS = {
class ClaudeProvider(AIProvider):
def __init__(self, api_key: str, model: str = "claude-sonnet-4-20250514"):
+ try:
+ from anthropic import AsyncAnthropic
+ except ImportError:
+ raise ImportError(
+ "anthropic SDK is required for ClaudeProvider. "
+ "Install it with: pip install anthropic"
+ )
self.client = AsyncAnthropic(api_key=api_key)
self.model = model
self._name = f"claude-sonnet"
diff --git a/backend/app/ai/providers/openai.py b/backend/app/ai/providers/openai.py
index 52ae6fa..e166330 100644
--- a/backend/app/ai/providers/openai.py
+++ b/backend/app/ai/providers/openai.py
@@ -1,6 +1,5 @@
from typing import Dict, Any, Optional
import json
-from openai import AsyncOpenAI
from app.ai.base import AIProvider
@@ -20,6 +19,13 @@ SYSTEM_PROMPTS = {
class OpenAIProvider(AIProvider):
def __init__(self, api_key: str, model: str = "gpt-4o", base_url: Optional[str] = None):
+ try:
+ from openai import AsyncOpenAI
+ except ImportError:
+ raise ImportError(
+ "openai>=1.0 is required for OpenAIProvider. "
+ "Install it with: pip install 'openai>=1.0'"
+ )
kwargs = {"api_key": api_key}
if base_url:
kwargs["base_url"] = base_url
diff --git a/backend/app/ai/providers/spark.py b/backend/app/ai/providers/spark.py
index 0a7b113..2ec0384 100644
--- a/backend/app/ai/providers/spark.py
+++ b/backend/app/ai/providers/spark.py
@@ -1,6 +1,5 @@
from typing import Dict, Any, Optional
import json
-from openai import AsyncOpenAI
from app.ai.base import AIProvider
@@ -18,6 +17,10 @@ SYSTEM_PROMPTS = {
class SparkProvider(AIProvider):
def __init__(self, api_key: str, model: str = "astron-code-latest", base_url: str = None):
from app.config import settings
+ try:
+ from openai import AsyncOpenAI
+ except ImportError:
+ raise ImportError("openai>=1.0 is required for SparkProvider")
self.client = AsyncOpenAI(
api_key=api_key,
base_url=base_url or settings.IFLYTEK_API_BASE,
diff --git a/backend/app/api/v1/admin.py b/backend/app/api/v1/admin.py
index e81623d..09492ca 100644
--- a/backend/app/api/v1/admin.py
+++ b/backend/app/api/v1/admin.py
@@ -1,6 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
-from typing import Annotated
from app.database import get_db
from app.services.admin import AdminService
from app.api.v1.deps import get_current_user
@@ -17,7 +16,7 @@ async def require_admin(current_user: dict = Depends(get_current_user)) -> dict:
@router.get("/dashboard")
async def get_dashboard(
_: dict = Depends(require_admin),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = AdminService(db)
return await service.get_dashboard()
@@ -28,7 +27,7 @@ async def list_users(
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100),
_: dict = Depends(require_admin),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = AdminService(db)
return await service.list_users(page, size)
@@ -39,7 +38,7 @@ async def update_user_tier(
target_user_id: str,
data: dict,
_: dict = Depends(require_admin),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = AdminService(db)
tier = data.get("tier")
@@ -55,7 +54,7 @@ async def update_user_tier(
async def toggle_user_active(
target_user_id: str,
_: dict = Depends(require_admin),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = AdminService(db)
success = await service.toggle_user_active(target_user_id)
@@ -66,7 +65,7 @@ async def toggle_user_active(
@router.get("/health")
async def system_health(
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = AdminService(db)
return await service.get_system_health()
diff --git a/backend/app/api/v1/analytics.py b/backend/app/api/v1/analytics.py
index 4fe689f..b7e3630 100644
--- a/backend/app/api/v1/analytics.py
+++ b/backend/app/api/v1/analytics.py
@@ -1,6 +1,5 @@
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
-from typing import Annotated
from app.database import get_db
from app.services.analytics import AnalyticsService
from app.api.v1.deps import get_current_user_id
@@ -11,7 +10,7 @@ router = APIRouter()
@router.get("/customers")
async def customer_analytics(
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = AnalyticsService(db)
return await service.get_customer_stats(user_id)
@@ -20,7 +19,7 @@ async def customer_analytics(
@router.get("/translations")
async def translation_analytics(
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = AnalyticsService(db)
return await service.get_translation_stats(user_id)
@@ -29,7 +28,7 @@ async def translation_analytics(
@router.get("/quotations")
async def quotation_analytics(
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = AnalyticsService(db)
return await service.get_quotation_stats(user_id)
@@ -38,7 +37,7 @@ async def quotation_analytics(
@router.get("/messages")
async def message_analytics(
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = AnalyticsService(db)
return await service.get_message_stats(user_id)
@@ -47,7 +46,7 @@ async def message_analytics(
@router.get("/overview")
async def overview(
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = AnalyticsService(db)
customers = await service.get_customer_stats(user_id)
@@ -67,7 +66,7 @@ async def overview(
@router.get("/marketing")
async def marketing_analytics(
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = AnalyticsService(db)
return await service.get_marketing_stats(user_id)
diff --git a/backend/app/api/v1/auth.py b/backend/app/api/v1/auth.py
index 452e659..0a14497 100644
--- a/backend/app/api/v1/auth.py
+++ b/backend/app/api/v1/auth.py
@@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends, HTTPException, status, Header
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
-from typing import Annotated, Optional
+from typing import Optional
import uuid
from app.database import get_db
from app.models.user import User
@@ -31,7 +31,7 @@ class RefreshRequest(BaseModel):
@router.post("/register")
-async def register(data: RegisterRequest, db: Annotated[AsyncSession, Depends(get_db)] = None):
+async def register(data: RegisterRequest, db: AsyncSession = Depends(get_db)):
existing = await db.execute(select(User).where(User.phone == data.phone))
if existing.scalar_one_or_none():
raise HTTPException(status_code=400, detail="Phone already registered")
@@ -56,8 +56,8 @@ async def register(data: RegisterRequest, db: Annotated[AsyncSession, Depends(ge
@router.post("/login", response_model=LoginResponse)
async def login(
- form: Annotated[OAuth2PasswordRequestForm, Depends()],
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ form: OAuth2PasswordRequestForm = Depends(),
+ db: AsyncSession = Depends(get_db),
):
result = await db.execute(select(User).where(User.phone == form.username))
user = result.scalar_one_or_none()
@@ -128,7 +128,7 @@ async def refresh(data: RefreshRequest):
@router.get("/me")
async def get_me(
authorization: Optional[str] = Header(None, alias="Authorization"),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Missing token")
@@ -178,8 +178,17 @@ class WeChatLoginRequest(BaseModel):
iv: str = ""
+@router.get("/wechat/config")
+async def wechat_config():
+ from app.config import settings
+ return {
+ "available": bool(settings.WECHAT_APP_ID and settings.WECHAT_APP_SECRET),
+ "app_id": settings.WECHAT_APP_ID or "",
+ }
+
+
@router.post("/wechat-login")
-async def wechat_login(data: WeChatLoginRequest, db: Annotated[AsyncSession, Depends(get_db)] = None):
+async def wechat_login(data: WeChatLoginRequest, db: AsyncSession = Depends(get_db)):
from app.services.wechat import wechat_service
session = await wechat_service.code2session(data.code)
@@ -216,7 +225,7 @@ async def wechat_login(data: WeChatLoginRequest, db: Annotated[AsyncSession, Dep
async def update_settings(
data: SettingsUpdate,
authorization: Optional[str] = Header(None, alias="Authorization"),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Missing token")
diff --git a/backend/app/api/v1/customer.py b/backend/app/api/v1/customer.py
index 79a430a..acf08e2 100644
--- a/backend/app/api/v1/customer.py
+++ b/backend/app/api/v1/customer.py
@@ -1,6 +1,6 @@
from fastapi import APIRouter, Depends, HTTPException, Query, UploadFile, File, Response
from sqlalchemy.ext.asyncio import AsyncSession
-from typing import Annotated, Optional, List
+from typing import Optional, List
from app.database import get_db
from app.services.customer import CustomerService
from app.services.customer_health import CustomerHealthService
@@ -18,7 +18,7 @@ async def list_customers(
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100),
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = CustomerService(db)
return await service.list_customers(user_id, status, page, size)
@@ -28,7 +28,7 @@ async def list_customers(
async def get_silent(
days: int = Query(3, ge=1),
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = CustomerService(db)
customers = await service.get_silent_customers(user_id, days)
@@ -43,7 +43,7 @@ async def get_silent(
async def get_customer(
customer_id: str,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = CustomerService(db)
customer = await service.get_customer(user_id, customer_id)
@@ -56,7 +56,7 @@ async def get_customer(
async def create_customer(
data: dict,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = CustomerService(db)
customer = await service.create_customer(user_id, data)
@@ -68,7 +68,7 @@ async def update_customer(
customer_id: str,
data: dict,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = CustomerService(db)
customer = await service.update_customer(user_id, customer_id, data)
@@ -81,7 +81,7 @@ async def update_customer(
async def delete_customer(
customer_id: str,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = CustomerService(db)
deleted = await service.delete_customer(user_id, customer_id)
@@ -94,7 +94,7 @@ async def delete_customer(
async def import_customers(
file: UploadFile = File(...),
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
from app.workers.tasks import process_customer_import
@@ -135,7 +135,7 @@ async def import_customers(
async def export_customers(
status: Optional[str] = None,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = CustomerService(db)
result = await service.list_customers(user_id, status, 1, 9999)
@@ -151,7 +151,7 @@ async def export_customers(
@router.get("/health-overview")
async def get_health_overview(
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = CustomerHealthService(db)
return await service.get_health_overview(user_id)
@@ -160,7 +160,7 @@ async def get_health_overview(
@router.get("/health-scores")
async def get_all_health_scores(
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = CustomerHealthService(db)
return {"items": await service.get_all_health_scores(user_id)}
@@ -170,7 +170,7 @@ async def get_all_health_scores(
async def get_customer_health(
customer_id: str,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = CustomerHealthService(db)
health = await service.get_customer_health(user_id, customer_id)
@@ -185,7 +185,7 @@ async def get_conversation(
page: int = Query(1, ge=1),
size: int = Query(50, ge=1, le=200),
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = CustomerService(db)
return await service.get_conversation(user_id, customer_id, page, size)
diff --git a/backend/app/api/v1/feedback.py b/backend/app/api/v1/feedback.py
index 63f2b66..fa81b32 100644
--- a/backend/app/api/v1/feedback.py
+++ b/backend/app/api/v1/feedback.py
@@ -1,6 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
-from typing import Annotated
from pydantic import BaseModel
from app.database import get_db
from app.models.feedback import Feedback
@@ -19,7 +18,7 @@ class FeedbackRequest(BaseModel):
async def submit_feedback(
data: FeedbackRequest,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
if not data.content.strip():
raise HTTPException(status_code=400, detail="Content is required")
diff --git a/backend/app/api/v1/followup.py b/backend/app/api/v1/followup.py
index 43a8af4..09ac3c4 100644
--- a/backend/app/api/v1/followup.py
+++ b/backend/app/api/v1/followup.py
@@ -1,6 +1,6 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
-from typing import Annotated, Optional
+from typing import Optional
from app.database import get_db
from app.services.followup_engine import FollowupEngine
from app.api.v1.deps import get_current_user_id
@@ -11,7 +11,7 @@ router = APIRouter()
@router.get("/strategies")
async def list_strategies(
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
engine = FollowupEngine(db)
await engine.ensure_default_strategies()
@@ -23,7 +23,7 @@ async def get_pending_followups(
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100),
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
engine = FollowupEngine(db)
return await engine.get_pending_followups(user_id, page, size)
@@ -34,7 +34,7 @@ async def get_followup_logs(
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100),
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
engine = FollowupEngine(db)
return await engine.get_followup_logs(user_id, page, size)
@@ -44,7 +44,7 @@ async def get_followup_logs(
async def mark_followup_sent(
log_id: str,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
engine = FollowupEngine(db)
success = await engine.mark_sent(user_id, log_id)
@@ -58,7 +58,7 @@ async def edit_and_send_followup(
log_id: str,
body: dict,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
edited_text = body.get("edited_text", "")
if not edited_text:
@@ -73,7 +73,7 @@ async def edit_and_send_followup(
@router.get("/stats")
async def get_followup_stats(
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
engine = FollowupEngine(db)
return await engine.get_stats(user_id)
@@ -82,7 +82,7 @@ async def get_followup_stats(
@router.post("/scan")
async def trigger_followup_scan(
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
engine = FollowupEngine(db)
result = await engine.scan_and_followup()
diff --git a/backend/app/api/v1/interaction.py b/backend/app/api/v1/interaction.py
index 9bbb7f7..68cee9f 100644
--- a/backend/app/api/v1/interaction.py
+++ b/backend/app/api/v1/interaction.py
@@ -1,6 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
-from typing import Annotated
from app.database import get_db
from app.services.preference import UserPreferenceService
from app.services.marketing_effect import MarketingEffectService
@@ -13,7 +12,7 @@ router = APIRouter()
async def record_selection(
data: dict,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
message_id = data.get("message_id")
selected_index = data.get("selected_index")
@@ -30,7 +29,7 @@ async def record_selection(
async def record_edit(
data: dict,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
message_id = data.get("message_id")
edited_text = data.get("edited_text")
@@ -46,7 +45,7 @@ async def record_edit(
@router.post("/analyze")
async def analyze_preferences(
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = UserPreferenceService(db)
preferences = await service.analyze_preferences(user_id)
@@ -56,7 +55,7 @@ async def analyze_preferences(
@router.get("/preferences")
async def get_preferences(
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = UserPreferenceService(db)
return await service.get_analysis(user_id)
@@ -66,7 +65,7 @@ async def get_preferences(
async def track_marketing_effect(
data: dict,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = MarketingEffectService(db)
result = await service.track_event(
@@ -87,7 +86,7 @@ async def get_marketing_effects(
page: int = 1,
size: int = 20,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = MarketingEffectService(db)
return await service.get_effects(user_id, page, size)
@@ -96,7 +95,7 @@ async def get_marketing_effects(
@router.get("/marketing-effects/stats")
async def get_marketing_effect_stats(
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = MarketingEffectService(db)
return await service.get_stats(user_id)
diff --git a/backend/app/api/v1/marketing.py b/backend/app/api/v1/marketing.py
index 71325ce..3727a4a 100644
--- a/backend/app/api/v1/marketing.py
+++ b/backend/app/api/v1/marketing.py
@@ -1,5 +1,5 @@
from fastapi import APIRouter, HTTPException, Depends
-from typing import Optional, Annotated
+from typing import Optional
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
@@ -43,7 +43,7 @@ class CompetitorRequest(BaseModel):
async def generate_marketing(
data: MarketingRequest,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = MarketingService()
pref_service = UserPreferenceService(db)
diff --git a/backend/app/api/v1/notification.py b/backend/app/api/v1/notification.py
index 472c5f4..750efb1 100644
--- a/backend/app/api/v1/notification.py
+++ b/backend/app/api/v1/notification.py
@@ -1,6 +1,6 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
-from typing import Annotated, Optional
+from typing import Optional
from app.database import get_db
from app.services.notification import NotificationService
from app.api.v1.deps import get_current_user_id
@@ -14,7 +14,7 @@ async def list_notifications(
size: int = Query(20, ge=1, le=100),
unread_only: bool = Query(False),
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = NotificationService(db)
return await service.list_notifications(user_id, page, size, unread_only)
@@ -23,7 +23,7 @@ async def list_notifications(
@router.get("/unread-count")
async def unread_count(
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = NotificationService(db)
count = await service.get_unread_count(user_id)
@@ -34,7 +34,7 @@ async def unread_count(
async def mark_read(
notification_id: str,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = NotificationService(db)
success = await service.mark_read(user_id, notification_id)
@@ -46,7 +46,7 @@ async def mark_read(
@router.post("/read-all")
async def mark_all_read(
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = NotificationService(db)
count = await service.mark_all_read(user_id)
@@ -57,7 +57,7 @@ async def mark_all_read(
async def delete_notification(
notification_id: str,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = NotificationService(db)
success = await service.delete_notification(user_id, notification_id)
diff --git a/backend/app/api/v1/onboarding.py b/backend/app/api/v1/onboarding.py
index d7fe064..65643ff 100644
--- a/backend/app/api/v1/onboarding.py
+++ b/backend/app/api/v1/onboarding.py
@@ -1,6 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
-from typing import Annotated
from pydantic import BaseModel
from app.database import get_db
from app.services.onboarding import OnboardingService
@@ -19,7 +18,7 @@ class OnboardingRequest(BaseModel):
@router.get("/status")
async def get_status(
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = OnboardingService(db)
return await service.check_status(user_id)
@@ -29,7 +28,7 @@ async def get_status(
async def create_first_product(
data: OnboardingRequest,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
if not data.name.strip():
raise HTTPException(status_code=400, detail="Product name is required")
diff --git a/backend/app/api/v1/payment.py b/backend/app/api/v1/payment.py
index c4915d0..b0dc63e 100644
--- a/backend/app/api/v1/payment.py
+++ b/backend/app/api/v1/payment.py
@@ -1,6 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
-from typing import Annotated
from pydantic import BaseModel
from app.database import get_db
from app.services.payment import PaymentService
@@ -27,7 +26,7 @@ async def get_plans():
@router.get("/subscription")
async def get_subscription(
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
svc = PaymentService(db)
return await svc.get_current_subscription(user_id)
@@ -37,7 +36,7 @@ async def get_subscription(
async def create_order(
data: CreateOrderRequest,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
svc = PaymentService(db)
try:
@@ -49,7 +48,7 @@ async def create_order(
@router.post("/callback")
async def payment_callback(
data: PaymentCallbackRequest,
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
svc = PaymentService(db)
success = await svc.handle_payment_callback(data.payment_id, data.success)
diff --git a/backend/app/api/v1/product.py b/backend/app/api/v1/product.py
index f12e8d2..2b2a1bc 100644
--- a/backend/app/api/v1/product.py
+++ b/backend/app/api/v1/product.py
@@ -1,6 +1,6 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
-from typing import Annotated, Optional
+from typing import Optional
from app.database import get_db
from app.services.product import ProductService
from app.api.v1.deps import get_current_user_id
@@ -44,7 +44,7 @@ async def list_products(
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100),
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = ProductService(db)
return await service.list_products(user_id, category, page, size)
@@ -54,7 +54,7 @@ async def list_products(
async def get_product(
product_id: str,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = ProductService(db)
product = await service.get_product(user_id, product_id)
@@ -67,7 +67,7 @@ async def get_product(
async def create_product(
data: ProductCreate,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = ProductService(db)
product = await service.create_product(user_id, data.dict())
@@ -79,7 +79,7 @@ async def update_product(
product_id: str,
data: ProductUpdate,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = ProductService(db)
product = await service.update_product(user_id, product_id, data.dict(exclude_unset=True))
@@ -92,7 +92,7 @@ async def update_product(
async def delete_product(
product_id: str,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = ProductService(db)
deleted = await service.delete_product(user_id, product_id)
diff --git a/backend/app/api/v1/push.py b/backend/app/api/v1/push.py
index b8f3be7..6f19ff0 100644
--- a/backend/app/api/v1/push.py
+++ b/backend/app/api/v1/push.py
@@ -1,6 +1,6 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
-from typing import Annotated, Optional
+from typing import Optional
from pydantic import BaseModel
from app.database import get_db
from app.services.push import PushService
@@ -20,7 +20,7 @@ class DeviceRegisterRequest(BaseModel):
async def register_device(
data: DeviceRegisterRequest,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = PushService(db)
device = await service.register_device(
@@ -41,7 +41,7 @@ async def register_device(
async def unregister_device(
data: dict,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
client_id = data.get("client_id")
if not client_id:
@@ -56,7 +56,7 @@ async def unregister_device(
@router.get("/devices")
async def list_devices(
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = PushService(db)
devices = await service.get_user_devices(user_id)
diff --git a/backend/app/api/v1/quotation.py b/backend/app/api/v1/quotation.py
index f006789..9e63ccb 100644
--- a/backend/app/api/v1/quotation.py
+++ b/backend/app/api/v1/quotation.py
@@ -1,6 +1,6 @@
from fastapi import APIRouter, Depends, HTTPException, Query, Response
from sqlalchemy.ext.asyncio import AsyncSession
-from typing import Annotated, Optional
+from typing import Optional
from pydantic import BaseModel
from app.database import get_db
from app.services.quotation import QuotationService
@@ -23,7 +23,7 @@ class InquiryRequest(BaseModel):
async def generate_from_inquiry(
data: InquiryRequest,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = QuotationService(db)
result = await service.generate_from_inquiry(
@@ -38,7 +38,7 @@ async def generate_from_inquiry(
async def create_quotation(
data: dict,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = QuotationService(db)
try:
@@ -53,7 +53,7 @@ async def list_quotations(
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100),
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = QuotationService(db)
return await service.list_quotations(user_id, page, size)
@@ -63,7 +63,7 @@ async def list_quotations(
async def get_quotation(
quotation_id: str,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = QuotationService(db)
quotation = await service.get_quotation(user_id, quotation_id)
@@ -77,7 +77,7 @@ async def update_quotation_status(
quotation_id: str,
data: dict,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = QuotationService(db)
quotation = await service.update_status(user_id, quotation_id, data.get("status", "draft"))
@@ -89,7 +89,7 @@ async def update_quotation_status(
@router.get("/export/csv")
async def export_quotations(
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = QuotationService(db)
result = await service.list_quotations(user_id, 1, 9999)
@@ -106,7 +106,7 @@ async def export_quotations(
async def export_quotation_pdf(
quotation_id: str,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = QuotationService(db)
quotation = await service.get_quotation(user_id, quotation_id)
diff --git a/backend/app/api/v1/silent_pattern.py b/backend/app/api/v1/silent_pattern.py
index 6b50856..291c926 100644
--- a/backend/app/api/v1/silent_pattern.py
+++ b/backend/app/api/v1/silent_pattern.py
@@ -1,6 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
-from typing import Annotated
from app.database import get_db
from app.services.silent_pattern import SilentPatternService
from app.api.v1.deps import get_current_user_id
@@ -11,7 +10,7 @@ router = APIRouter()
@router.get("/risk-analysis")
async def get_silent_risk_analysis(
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = SilentPatternService(db)
risks = await service.analyze_silent_risk(user_id)
@@ -27,7 +26,7 @@ async def get_silent_risk_analysis(
async def get_followup_suggestions(
customer_id: str,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = SilentPatternService(db)
suggestions = await service.get_suggestions(user_id, customer_id)
diff --git a/backend/app/api/v1/teams.py b/backend/app/api/v1/teams.py
index c92698f..5f2f273 100644
--- a/backend/app/api/v1/teams.py
+++ b/backend/app/api/v1/teams.py
@@ -1,6 +1,6 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
-from typing import Annotated, Optional
+from typing import Optional
from pydantic import BaseModel
from app.database import get_db
from app.services.team import TeamService
@@ -26,7 +26,7 @@ class UpdateRoleRequest(BaseModel):
async def create_team(
data: CreateTeamRequest,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = TeamService(db)
try:
@@ -39,7 +39,7 @@ async def create_team(
@router.get("")
async def list_teams(
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = TeamService(db)
return {"teams": await service.list_user_teams(user_id)}
@@ -49,7 +49,7 @@ async def list_teams(
async def get_team(
team_id: str,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = TeamService(db)
team = await service.get_team(team_id, user_id)
@@ -63,7 +63,7 @@ async def invite_member(
team_id: str,
data: InviteRequest,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = TeamService(db)
try:
@@ -78,7 +78,7 @@ async def remove_member(
team_id: str,
member_id: str,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = TeamService(db)
success = await service.remove_member(team_id, user_id, member_id)
@@ -91,7 +91,7 @@ async def remove_member(
async def leave_team(
team_id: str,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = TeamService(db)
success = await service.leave_team(team_id, user_id)
@@ -106,7 +106,7 @@ async def update_member_role(
member_id: str,
data: UpdateRoleRequest,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
service = TeamService(db)
if data.role not in ("admin", "member", "viewer"):
diff --git a/backend/app/api/v1/training.py b/backend/app/api/v1/training.py
index fe116a5..ce00d1b 100644
--- a/backend/app/api/v1/training.py
+++ b/backend/app/api/v1/training.py
@@ -1,6 +1,5 @@
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
-from typing import Annotated
from app.database import get_db
from app.services.corpus_trainer import CorpusTrainer
from app.api.v1.deps import get_current_user_id
@@ -10,7 +9,7 @@ router = APIRouter()
@router.post("/corpus/run")
async def run_corpus_training(
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
trainer = CorpusTrainer(db)
result = await trainer.run_pipeline()
@@ -20,7 +19,7 @@ async def run_corpus_training(
@router.post("/corpus/embeddings")
async def compute_embeddings(
batch_size: int = 50,
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
trainer = CorpusTrainer(db)
result = await trainer.compute_embeddings(batch_size)
@@ -29,7 +28,7 @@ async def compute_embeddings(
@router.get("/corpus/stats")
async def corpus_stats(
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
trainer = CorpusTrainer(db)
return await trainer.get_stats()
@@ -37,7 +36,7 @@ async def corpus_stats(
@router.post("/corpus/deduplicate")
async def deduplicate_corpus(
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
trainer = CorpusTrainer(db)
result = await trainer.deduplicate()
diff --git a/backend/app/api/v1/translate.py b/backend/app/api/v1/translate.py
index 30a7c16..774459b 100644
--- a/backend/app/api/v1/translate.py
+++ b/backend/app/api/v1/translate.py
@@ -1,5 +1,5 @@
from fastapi import APIRouter, HTTPException, Response, Depends
-from typing import Optional, Dict, Any, Annotated
+from typing import Optional, Dict, Any
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
@@ -51,7 +51,7 @@ async def translate_text(
async def generate_reply(
data: ReplyRequest,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
pref_service = UserPreferenceService(db)
pref_context = await pref_service.get_preference_context(user_id, "reply")
diff --git a/backend/app/api/v1/whatsapp.py b/backend/app/api/v1/whatsapp.py
index 94ba188..e86d1a6 100644
--- a/backend/app/api/v1/whatsapp.py
+++ b/backend/app/api/v1/whatsapp.py
@@ -1,7 +1,7 @@
from fastapi import APIRouter, Request, HTTPException, Depends, Header
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, and_
-from typing import Annotated, Optional
+from typing import Optional
from pydantic import BaseModel
from app.database import get_db
from app.services.whatsapp import WhatsAppService
@@ -30,7 +30,7 @@ async def verify_webhook(
async def handle_webhook(
request: Request,
x_hub_signature_256: Optional[str] = Header(None),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
svc = WhatsAppService()
body = await request.body()
@@ -80,7 +80,7 @@ class SendMessageRequest(BaseModel):
async def send_message(
data: SendMessageRequest,
user_id: str = Depends(get_current_user_id),
- db: Annotated[AsyncSession, Depends(get_db)] = None,
+ db: AsyncSession = Depends(get_db),
):
svc = WhatsAppService()
diff --git a/uni-app/src/pages/customers/customers.vue b/uni-app/src/pages/customers/customers.vue
index c24b984..1a1ecda 100644
--- a/uni-app/src/pages/customers/customers.vue
+++ b/uni-app/src/pages/customers/customers.vue
@@ -796,7 +796,7 @@ const deleteCustomer = async (id) => {
.bottom-actions {
position: fixed;
right: 40rpx;
- bottom: 40rpx;
+ bottom: 100px;
display: flex;
flex-direction: column;
gap: 24rpx;
diff --git a/uni-app/src/pages/index/index.vue b/uni-app/src/pages/index/index.vue
index bac32d3..e403842 100644
--- a/uni-app/src/pages/index/index.vue
+++ b/uni-app/src/pages/index/index.vue
@@ -5,7 +5,11 @@
{{ userInfo?.username || '用户' }}
{{ userInfo?.tier === 'pro' ? 'Pro' : '免费版' }}
-
+
+
@@ -58,7 +62,10 @@
{{ tryResult }}
@@ -92,39 +99,6 @@
暂无待跟进客户
-
-
-
- 品
-
- 产品库
-
-
-
- 跟
-
-
- 跟进
- {{ followupStats.pending > 99 ? '99+' : followupStats.pending }}
-
-
-
-
- 数
-
- 数据
-
-
-
- 知
-
-
- 通知
- {{ unreadCount > 99 ? '99+' : unreadCount }}
-
-
-
-
待跟进提醒
@@ -138,7 +112,7 @@
- 更多功能
+ 功能矩阵
📦
@@ -221,15 +195,39 @@
跳过,以后再说
+
+
+
+ 📢 系统公告
+
+ 欢迎使用外贸小助手!
+ 登录后可解锁以下功能:
+ • 客户管理 — 管理客户信息与跟进记录
+ • 报价单 — 快速生成并导出专业报价
+ • 数据分析 — 查看业务统计与趋势
+ • 营销素材 — AI 生成营销文案与关键词
+ 现在登录体验全部功能 🚀
+
+
+
+
diff --git a/uni-app/src/pages/login/login.vue b/uni-app/src/pages/login/login.vue
index 20981e1..8fdfff1 100644
--- a/uni-app/src/pages/login/login.vue
+++ b/uni-app/src/pages/login/login.vue
@@ -1,5 +1,9 @@
+
+ 正在自动登录...
+
+
TradeMate
外贸小助手
@@ -110,7 +114,7 @@