From 23a31f7c009fdbb79c1bf72d77e926b0912c8ced Mon Sep 17 00:00:00 2001 From: TradeMate Dev Date: Thu, 14 May 2026 00:30:48 +0800 Subject: [PATCH] feat: silent wechat login, marketing tab optimization, admin page foundation - Add silent WeChat login for MP/browser environments - Fix Python 3.6 compatibility (remove typing.Annotated usage) - Marketing page: tab-based content generation with category support - Translate page: add auto-detect language default - Homepage: add TTS playback, announcement ticker, remove redundant quick-actions - Fix FAB button overlap with custom tabbar on customers/quotation pages - Make openai/anthropic imports lazy for Python 3.6 compat --- backend/.coverage | Bin 0 -> 69632 bytes backend/app/ai/providers/claude.py | 8 +- backend/app/ai/providers/openai.py | 8 +- backend/app/ai/providers/spark.py | 5 +- backend/app/api/v1/admin.py | 11 +- backend/app/api/v1/analytics.py | 13 +- backend/app/api/v1/auth.py | 23 +- backend/app/api/v1/customer.py | 26 +-- backend/app/api/v1/feedback.py | 3 +- backend/app/api/v1/followup.py | 16 +- backend/app/api/v1/interaction.py | 15 +- backend/app/api/v1/marketing.py | 4 +- backend/app/api/v1/notification.py | 12 +- backend/app/api/v1/onboarding.py | 5 +- backend/app/api/v1/payment.py | 7 +- backend/app/api/v1/product.py | 12 +- backend/app/api/v1/push.py | 8 +- backend/app/api/v1/quotation.py | 16 +- backend/app/api/v1/silent_pattern.py | 5 +- backend/app/api/v1/teams.py | 16 +- backend/app/api/v1/training.py | 9 +- backend/app/api/v1/translate.py | 4 +- backend/app/api/v1/whatsapp.py | 6 +- uni-app/src/pages/customers/customers.vue | 2 +- uni-app/src/pages/index/index.vue | 258 ++++++++++++++-------- uni-app/src/pages/login/login.vue | 100 +++++++-- uni-app/src/pages/marketing/marketing.vue | 147 +++++++----- uni-app/src/pages/quotation/quotation.vue | 4 +- uni-app/src/pages/translate/translate.vue | 10 +- uni-app/src/utils/api.js | 1 + 30 files changed, 485 insertions(+), 269 deletions(-) create mode 100644 backend/.coverage diff --git a/backend/.coverage b/backend/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..0b7ab1a446667ecd1abea729893f39dbc969d5ca GIT binary patch literal 69632 zcmeHQdvqLEd7s&x-Py-HR+enNENLxUmSjs>%WucFB)?+IdawLW@Ord6k|tj5Zgxku z)fz>c|x1ZT=3}K^MvZ zWq>k38F$3Lo3IzyDCUSQc&xgkfoK z5AO?S(-*@TH5(q+l4@8>CA7GrYpIE_KB=0ur*moo%~?FNYP zWU|_nlAQ^kS7+LA1>@Uv^^#sJ01#E1NTC|x7US^Na8?~xvuY}?=89UhXo=Q9>pgb` z_{#Ef_8ls2lah_YU$CS#P_WdL-f;kmr?Uy9{a98>#V6HVTUfDL7*B(qm-J$5v+1db zo(?Cq6mDiJr|DWc6;>~)@o8O6ylDg55NYND`%dnznMcn5E(7NhOCypl93I;C=DT!-MA3pHV)Bd@f*;HSnT$(B6Vgh zBR~qYil8McFEH>&7s>!-fHFWCpbSt3C!-fHFWCpbSt3Cao3?mF4LEo4)Ob=OuK_Wk38K4YM1}FoR0m^{IfZ(oU z&C>v`fV<#`j_(1Sr;FZ9mfdT(( z{zv@1zE^#Z_y+i^{G)uIcftD!uj1uBPkByy814x!>i(tsF?XM9!S!L+0DF!72peTy zV;->7_{~?&L^vkDE0Rs8^~j`}olIk^DSd`kIUwg z@ZfJc1;C@N0KAa|*l0c23x-tzbg%_L*Ej&0P&26YCz=7TlLXJAGh;n~47kztfJ@f( zTG3Hr@Gx>_8tHnv2{3n%=&ESQ3omF1c(MyzEd_t+3kpCVUkA_;=b$r6_B?`J>nLn8 z9aoYFw$V}8gsNr`?1?o1OJoZr{Kk{YbOPDziLj%%>5Q6EG{8O80Jz&6>$F%*OKEy6 z2FOS20lAtGc}&Tn*7w%|RvjT$7M{OUvw$|Z8qmo6UyImUdb}3!hz#HIfB4aY^Y9E~ zY68?eRs*ok%qKQwN2>wVi4oPVYP1SKiAJiabV5z$BID^~GJSD61I-_<1k5IqJ~uEE zDvAaWp{qyX5TrT*3FP7 zDgmeyzGMsd21SRcgx_C5BWP4`NJrbi)E5NIS`vRp4{I9}#$bT7!A9H&Kk|WM`>3C^ z!3LhK!9za4B#OcjrVVhI2S6eN2g_C=ri)K!HGKv(+e_MPj5wtw5=r$U{Mrb@^f-d4 zUW%)Ds6<1>Fy~0iZCv)c0kM+A8sQf(#x*pLA9ev$ISJI1qM>A{j|Cti4=FYfa`8~a z1)r?^6w1qG`6;i@bIQYVAN73FeU%&aKI#6Tc#jwno@bwMUE|+yJuZi(=cGT8){Ea1 z?vu|8U4g;CtHB3-kNO|=4fCt|@Fo5+c9eOYd64gS_qzt!>j?h0U2fy@ zyI2ystZ3?n{6BI#m*4FSwmG^k<^LVWxqQ2`;Lz#qqg=j~2-O&QOoainZJ5iiCBn2Q zF6RFmPjUGz4gr?r{~L~R`He)tM(geQf9nvJU*iO*k^i@xk^irA7S_!F*E$Pp=Kqbvwot;amH)4C7T3!E!>76Y zHs?CcHiI_wartUeWFwrbAK>zJq*zw|Uq|l$C2`kkVpFpG-=0+0Iyax#x~0ac5!J40 zHStJgq-wVOziNcbHxc!@fobLcmBfS^h$W-+Dq_zxP?zQZD;?u>DgUot&H z864R2{}se;YJeK~f0UUefCpkr>k^hTM>1Z36gnlkxOXTmi z{69$CU?Xlc^8WyFgAF`egZ;!&IKs36`iKo2EW0HC=ZTw*5lixauQQk>`M<}RmfN`G zj&k`*B5SnrfAk38K4YM1}FoR z0m=Yn;E##{H|u6Z^!z_7Ut{2pE|dYv0A+wOKpCJ6PzERilmW^BWq>k38K4Zj1sQO= zML)X!KN9F+%Ri96EB}-HIr-!Aee%2HOY%8+NIoog%MrOjT9oFcZ%JQ~u1HTx z4@&QZ-wBwKVp6}fS89>!B%in-&Wm3WKPx^ZJ|ezL%!|6Hi6_ND@u0X%Y!U0l3ehWE z6MiOqU--80RpAT5)562Ty~2cWM5q#6!K=Y%gC7gNFZgFcJ*Wi-gBybWz%K*;8F)JI z{=i*k38TdcIK+Oop`nPYJt=`YD@JrI55f_560W=_U+=Xz=fp>XH^!e%%MUYQ++f3Ubfa7<6-0PNf} zFpGaaYBkz{7Z1L3rV+KB%{OPhqp0ftOtszd%6M*w9hf z)3^gchr&K4#JC!Ip`@p|pt?eHsEWDxpa#>!^vpFLfeN>7Im;vHWsC9k;p)RsXKQca zsV^e}&^y3!{OSjuxo`+o;TT`IqYFyUb`%2t3tDG3c*oNxJP-pSEf+x5YwyIV^`KK3I-xgG(yNK^~Jkf{tc*p@TC`<*{(S7B=RYxx=`7y+DIoSP#WDo)>b@?dq1!d7EHM;@GlvAlJE{yZnWxKlk0Y zeRp=Yo2^(2MH{vjA`SD%8Lm7GKE_`e$dD%jNZGe`g`~92|f3S^g?p z&vc1(P`G+`;gvTSrh$>mR>S7H?S)qsuiaIccVC|8uL`wLRCA=TdE(N`Y<-{xwpJY| zyfX7LD^{ap56!-D58rjg-4LpR&Bp4&{flqBaT!_nxfPXAvi@k{6?Wcz#VvO+^Sm5d zg=!7Y&dl@OSD9|6evfY@l&m;hc;&Lc0@f;arn;DO{&KY1a<-nCuUi4Dd)f;3L;cHh zaFyF&ho9r+t}6}oWw5&|P>6he0@wyytzsDA3h;ONA>_GTQV90fchAneJpb%HJab8s z(e~b1fBm(2{tCa-3w`02{H#|*(#OgI$ng5JA6`UW1Gc>?2n8K=v+#2}XbI!L-bOYQ zfbzz54;>dmFy6ou{o`Igf@s=YL`y!h&?@89Be!G^Rt z0FU|m{Su3=|Br}YW#G5}z9oAh%fBl9T>5YMarv+1KbQUkKkfgw(z_w+&r4sHJ|KMz zGXM8VpOQW(pOJD>yZj6JY5Aw}3-VXw=jDHw|3&`1{3ZEG$t}NKt`NT?{;PbibW$3Y zwo3cuP2x*ZKo-TX%3b0$`GV9d{Yc&+z9v5)CFT1hRc;jL<$kG39+ev8w0uY^lND*b z+$z2(*GgyQNohCYfG(5)$^d16GC&!i3{VCr1C#;E0A+wO01TjXkli+d#l~SQHVk30 ze-Mj-0W3E6W3i_XiycR?Xzs;g%MmPk4`XrgRxEBkghk&$ENTy65!sK$$UZDK^6D;Br3VBu}X!m}O=t_chGIxM!X#iF$li@`Nm>8{Mou7-dY#HqREd%*oVbh9*agV7Hd3MG;mndyRlg9;@|`TzW!f4 zQAd4TC{03Y>y(tVX1^*-tTpm>iM5xRo^BRtPO;kw3uDDYn2 zkNiL3-w6CDxIb{fJtDl@74_d2{40N#>v8tF?|pI@|7O5?{)G5V=0U#S-R~M?um3I_ z-p1v35xp+adYiH~l}Tsyn6V8nzK`6_<##)SZN6PKHmNE}eGRBa~ODehwszK#?to1WIyETGkq`+v!Mmsk4+ znLtsyK`!4wq-PwxfjO%tG&Hc>LOuY%8=^0CM&M=1lS*m=QMuEp5p@dOQ`NQ91b|k{ z=a~Vbb{ns1A5SNfX>tsQzT;DE>5G#ZrCYI7@f6Lk;6f9Pb_wJPD`pO zJ(f{)F!vN_+fP2!6_K?RAS!WPOC#8$j?H+=%oJ!0kVl;2bzD^wV@e#&ABV|D&SL9R zXDyL&V{pG2y3EV`(LuC|bb*M_vjKF0ZS~6dFVJIpyz4N=eS>S{#9PJEf(P zn9`6f?Ij+{9P8p_cg7iB=q7BeD!sVNYMqxbrOTk>Q&g=Jl%6%a4<-i@@ zCj+ZoFZln?A9pWsPkMaZY5pmHkmtO&`vUHVJ?DI%@+s_(`O~g$*I)iFSwB7I_r?IR z!&&DgE~*emqKW!A@vMR@7iEw(lRQT3#U`k()*UiZbLJ_6_0Sz}vX|UZU~HvIi>%h^gb_F!}&1eZ-bwp=YePu$MSCwm_Ff zakmmjhZb^49Cwh|mMvTq$pNwZiKBQ6(H_e65J&M#utqS~kML;Go9-B~8kjUw2bT(v*b9QAvbGw;1y0tXcbu^Z5JjLa= zI5TXSCxeT5>;~tCfJ{~$SF>s=j^>AE=ZLk&S%9r|Zfdyk`iOI(f_e#EdjWknIX4HJ zmoR?s*IbDI@j+L%-bgST3H`aCQcIt|T624P5*1 zA`^F!0b1g+tDK@>S(H&u>{o`iW)upvRyl^bbgry|c;+_rOsE$$JP1}grKRcn&~nKs zEsJ3{AcmYWQF~;y!l|a)#t`8MmoF!p1kG~^=>Go*^8h1%TD}9`^7l1(uV0h+4e@TV zL-?NX9$|0rr@;q;2LrDJ9t{lmU-LiW@AbXvd&D=uU*#X=`@9R@Pk0qC?|I5|(!+31 za8dU!-H*BZTnnxby9U^6>_^xr^BTba_RERB9BVev2!L%qY#%3os0N@s90F4Xv#n^1L6@WLA02{40 zLp~Ik4YmO28V5j)>;EU30k4w;&!V$rHj1tXT(Yje;rjpSCcxZ5qHD?ZY5Vp6-ztx2Eg6sSf|;hs-yLQ zTuq2tN{1FSkiEbIFJ;A%i4^Z%0T|Ho?qkI3*X|F~Kq|cYfqQ_Q|5^5lpjM67o0xFTm8>q|Drf7vzoG!)pC(0et zby`=4qnQc@8P$zuJj8IX; zBRWK-aq1x|IHaR(VCoA3W-W=oClp;VCXB%VX@iZp(YXE}^^-Q(z_T@Y$Oo82Q8>c1 z0S@y3NMzt(*(%)AXUmR$cI<{W9cjmus)AXbuCV?1qK|3B;k zsB#i0BWLe}w*``Uh^>Jr|6lJOW#o^`CuK(ZkTfDLh#wJ0g+<{B;fxRn{!MTqSP^(8 zFd3-y|ARm6uk=0RQ+;Lp7kQ0e;r)#FjHlrFsArJ-754#d(0$GQIDpZGGVuSGf#`AY zPokOI$^)z$Ek=(4CQ)Q%-VyGRDR2_w3#_0&vcpr!&u( zFWt8h9R_fsZZhU_6B+`nGedys%t^~)bZNU6Bs%DjzP6i<&kQ)G?+y2toazT`vQrAh zdpU1FzP%5S_mWtdRZFyvpFy4m#2WphRA^q^of+ z)X-js_%!d7AKn9i&UCqPbJgH(KwRU*SW>+Ae&80sBZ~#>4?!K<1#m>3V|9w{-mmCR zKy_lYvtf?z08ApAv@~6M2y|pSfD+lHInrCVs2$tp7^AjZ)S_D*V$_CtdJA9@U9DPT mblGih$2SACGvku|?A>T5z!KSj(Oq`X=q6HXEgG!bvi=tXp;KW1 literal 0 HcmV?d00001 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 @@