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' : '免费版' }} - + + 📢 + {{ announcements[currentAnnouncement] }} + + 👋 游客模式 @@ -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 @@