From 73ceb95bdd90e26bd913d0a14c114eb8218babee Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 24 Mar 2026 15:09:59 +0800 Subject: [PATCH] feat: implement image generation service Co-Authored-By: Claude Opus 4.6 --- .../__pycache__/image.cpython-310.pyc | Bin 0 -> 2575 bytes .../services/__pycache__/llm.cpython-310.pyc | Bin 0 -> 930 bytes .../__pycache__/prompt.cpython-310.pyc | Bin 0 -> 1984 bytes imagecreator/backend/app/services/image.py | 80 +++++++++++++++++- ...image_service.cpython-310-pytest-9.0.2.pyc | Bin 0 -> 861 bytes .../test_prompt.cpython-310-pytest-9.0.2.pyc | Bin 0 -> 2415 bytes .../backend/tests/test_image_service.py | 9 ++ 7 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 imagecreator/backend/app/services/__pycache__/image.cpython-310.pyc create mode 100644 imagecreator/backend/app/services/__pycache__/llm.cpython-310.pyc create mode 100644 imagecreator/backend/app/services/__pycache__/prompt.cpython-310.pyc create mode 100644 imagecreator/backend/tests/__pycache__/test_image_service.cpython-310-pytest-9.0.2.pyc create mode 100644 imagecreator/backend/tests/__pycache__/test_prompt.cpython-310-pytest-9.0.2.pyc create mode 100644 imagecreator/backend/tests/test_image_service.py diff --git a/imagecreator/backend/app/services/__pycache__/image.cpython-310.pyc b/imagecreator/backend/app/services/__pycache__/image.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..76716b108db3cc8107c43d427595e6755b1a2298 GIT binary patch literal 2575 zcmai0TW{1x6!whAmvuHFkdTU7t4dYX4NX>r6j6z`AV|}wBA_Nx%Sx4Hc4jx;?ApuB zERe{NXp>h|sp=abC6DeYNc9-lcrmv7E&K0F*baQ%Mq zk8}p4zwqJU<-)^d7-|uKJDhZ#MtLUA9&0dy@7%7}^BcaESvTl~jgUC= z4)?gf<#2zKHY%JfIkn(#u<0z;hzW1>b(&{M7i9lxHx-%2StYbiv$krY-oa}K9xlUB z>j1(*PrwVp>6X)Q1>>&pT9h;HZLv+#@GCw)&I1wf5QZzlmd`6Z+VUEe$`HJpuFchYnJB1Sif(3%%2XWKJyh{vIqTAYaWChgrF4gnCuBZ5grWA?J<(m(;ljf?( zcs=R&>#F>)DmAA2YbK22G)r|Hqb-FFL}dMgcl$fNuWNiM^gw3+2>^*;19%^V!s&qf z14mXh(X``o@{nLiI1fQl8&uLy<5jU%W73Bu5)DWOU`FQWt}iSu#8+-E#NW={k^#O! zCriK#8NbX(6=a3WBRWESvVP_e_e<@)(YEM8%u_SdpG~#8iCQs!U*^5OHeN61qT56# z_AfMx<4+jsXMmp|hK|!A9a_+WAf33|rYYgLUR9AoM7qBqIKE=0{|dj1xxa1L{(866UDu zKVP3bghKByyjNzb-p|_RNSgHrI)0V9{fS=M6R}?Fi>3!2>|Yol(%N$CHxLeRi*ZsA zU==Og0vPrf7}mDUiu;yr0kF*jw)w!Pl5GL-C*TzLbG`_GZRai9KzhkG;M@kZTVUIn z!Y`QRW(Bmr1`AP%JhE&XddW6>uT>Wf!!+6o)~Knx3PyemKFcwLafIUt69^{}Y%P&Z z@-)H+0JZQyRi|)v8Ubq~KS8J=Od`Cs)yH3D-X^!KDQYWR2Q>{S@oAON7ZLOKSmltq zp8dbN9QLAuw=mxWEa4sy^j_oA4QdhR4fFpIxiFt}g1kDMnI56;D9JRZgUROvWh-6QpNHjN~u_inFO>VtkaPq?br zh7HY?346(^;Hgw)_dRBtNW8Rs<2z_3@y#pC-xxL+q}+Ijvj+B^UNNkbrCGjuINO z=akYH(1V^+7CobGBA$CAMbX<)H~Ll^OUF_pTZN2%V6GkYxNFqyGYMetY%+ literal 0 HcmV?d00001 diff --git a/imagecreator/backend/app/services/__pycache__/llm.cpython-310.pyc b/imagecreator/backend/app/services/__pycache__/llm.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..410f67222b5473cfed3ef217918c164748da382a GIT binary patch literal 930 zcmZuv&2AGh5VrTn*@jJpf`rPA>mHgQBoL|)Dypg~q78_i*o(ESov^9*2irj;3MYE) zgYX#e7`}4i6*wSfHl$4@#+uRCo}X_%yYBV61mx|rAH{b{$WPpCiw~PaQ1cXwAc87V z(v(v4J>`{t>bJhHf-+1)N=}FfM0iO=_|;39VCN+1{DNGPP#3*!Y*AO40-qhJLRJ<} zjkLC?rY6U*ZRx<~5Y#*ZBS}gHNj*V@cS%xT?1(@@D;dh*0TE0F6DB$$x(uPM4{f`y zdwP0yF7>A(mpKe~E$HK5N1&z;#u7_sQ2C0k2=@hnY;O$EI$2Zj3%N)Fjqy4F<91Ju z&qm`BfB9y_Umd@5EZF1+mkSeO*k_Up8X|LfwS T=WCcR@M{|6GbS;OY2W(`V;|p9 literal 0 HcmV?d00001 diff --git a/imagecreator/backend/app/services/__pycache__/prompt.cpython-310.pyc b/imagecreator/backend/app/services/__pycache__/prompt.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bdd17a6d5ec08ed86d3c3096fd5b5ca4d855737f GIT binary patch literal 1984 zcmbtU-EP}96sCUcI7-rWX$CAs2OR8Tg8@guvR(|qS~N?HE3t~PPmb+1!qA{t)F>67DU{lGp zwU%-JDi33>#xP1L?{g;5M>khluAtjFI0ZFC)aBC|t=UqdtZ^9%F7<=JDEn}m?kHxVh{qLQXACZf7&bXf^OypP z5z;(h)c3r7q(CV203wf>-1ffmVV|-@@%|Wi{}zIsy}cH^XdiZeZ0qmQ!OkvjFp}-g z_dA{4Hpq0u)DV$*TRwcpKxQmLDx)$sB@9f5bH*~bkT-IP$NSocJriMssL94~bAeP> z@hXF-^P~!t_t=O36`)9G{rp%!;TcZ(6cY-caj7f^BwjxZ7o1ynM&Rv{x85c#~kSPh!IhwIL zO0 zXXBy~1U%s?2v)Uk?Pb}Te10cve|EiC(y1AghKjmiDi=wl^=CtOAwA8b9^yukx7drO z)?4zgyTO?vp|61zjaa`dvjFpRybMatS%tmd? zkoXDyklN@y3z^usn~C@67Nl64p%3O-Rit^C4Gm3C!^>mbOl{FXUTkE?e&-|>b*>!4 zl|6Ov%r%VFuH1?-*DzM=OdfS-L>b`oy>S~Rw&#hD(E8-Fiq^8HMJ5SBuva;g*ej9S zbwlf&oO2=HClxNyH51iCGhe?&urbfgmEo(A|pFWULsqH{p#(0q(jYJ~=A zSgXz>*PM2mwc Client: + if self._client is None: + self._client = Client(api_key=settings.GEMINI_API_KEY) + return self._client + + async def generate_text2image( + self, + prompt: str, + model: str = "gemini-2.5-flash" + ) -> dict: + response = self.client.models.generate_content( + model=model, + contents=prompt, + config={"response_modalities": ["TEXT", "IMAGE"]} + ) + + image_data = None + for part in response.parts: + if hasattr(part, 'inline_data') and part.inline_data: + image_data = base64.b64encode(part.inline_data.data).decode('utf-8') + break + + return { + "image_b64": image_data, + "model": model, + "finish_reason": response.candidates[0].finish_reason if hasattr(response, 'candidates') and response.candidates else None + } + + async def generate_image2image( + self, + prompt: str, + input_image_b64: str, + input_mime_type: str = "image/png", + model: str = "gemini-2.5-flash" + ) -> dict: + response = self.client.models.generate_content( + model=model, + contents=[ + {"text": prompt}, + {"inline_data": {"mime_type": input_mime_type, "data": input_image_b64}} + ], + config={"response_modalities": ["TEXT", "IMAGE"]} + ) + + image_data = None + for part in response.parts: + if hasattr(part, 'inline_data') and part.inline_data: + image_data = base64.b64encode(part.inline_data.data).decode('utf-8') + break + + return { + "image_b64": image_data, + "model": model, + "finish_reason": response.candidates[0].finish_reason if hasattr(response, 'candidates') and response.candidates else None + } + + def save_image(self, image_b64: str, user_id: str) -> str: + os.makedirs(settings.IMAGE_STORAGE_PATH, exist_ok=True) + filename = f"{user_id}/{uuid.uuid4()}.png" + filepath = os.path.join(settings.IMAGE_STORAGE_PATH, filename) + os.makedirs(os.path.dirname(filepath), exist_ok=True) + + image_data = base64.b64decode(image_b64) + with open(filepath, "wb") as f: + f.write(image_data) + + return f"/uploads/{filename}" diff --git a/imagecreator/backend/tests/__pycache__/test_image_service.cpython-310-pytest-9.0.2.pyc b/imagecreator/backend/tests/__pycache__/test_image_service.cpython-310-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..885fe90bd82403f171055940f12039336875b991 GIT binary patch literal 861 zcmY*Y&2H2%5VoCcl3liy_*=nkg;a@f*g{JY0)z?(Aue2c+l%Fmy-U<24z^ReRdQ*0 z3=Um!;x%{-Upes#JuyzUXtCt+%zQJMna_^g+8Q8OKc4?eIY#KWGd33i;}Euf0fHlr zGc?8RNpg=MjBy;a zD|CYN81P<=R#@$T7C5cIfIA(pF!#Tc5o$c%<3a7E7(<=&?8q%*q2r<=yJ zpGe(-)T_6+l(W_kYDig2a2SPdk@c<3+ko*N6Sp7@@ literal 0 HcmV?d00001 diff --git a/imagecreator/backend/tests/__pycache__/test_prompt.cpython-310-pytest-9.0.2.pyc b/imagecreator/backend/tests/__pycache__/test_prompt.cpython-310-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b1d54be36f8aeb2852aa3b468afcaaf728b0889b GIT binary patch literal 2415 zcmb7EOK;>v5O&)h+vAzZgRH`ahjv)BtC)o(vzw<_qAZUi0cmf!SkAPwn0UscwkMCl z781*y3*wYR=9miyegZ$k5*OgYnOnqxuiD;;M+*{Ia=E&?T=i9T*B7-~wu0x+t)GLI zswltFqWZw5?BR_+gy0Hip>n|FSM6!4!jztVP-Df8abRFa4b7f)V8Nzw{afXr&THJj z*XHc0;+TIRj>DZm6z@PY zN$WH0ldg^#uU)8%N~Uvzo2ec!=(WVcZauAy*>m;{^65&ZWolxlSZ69{nE~B^jtHrF ztY=ng#XsU7jY9*DI@u)-dx9gtKK=uaKIJA2IP4125qU_YUz5j6NN-|&H;MIqHJ1F# z7&G}bQCs7}Ks|Ndj{r61x+zb)yP zYoXtF3}N9WQ<{#Tdo3E}a*J#`quC0c4ie-#JUh9D8Zk6#&cRvJr3oEa38PbA*x52*JGz*q{1Xsj4$D!sAE-pk!{{^x@j$*16~pSgiGP+n40_%( zKezV==ZPODt|#(&x9f$WEBt}zVz@ow=S^(8hr=LDf<8_ljNlRHx83-QXc%(o>OKpj zLoamuUeC|#ZtNZT6d|8^B98{d@QDzS$QRtBNc6nK_0I;O*O!zbW(ZhK*5$Nv6K6>j zA`CJ&D0k7%ht)-=SQOFLtBcL5+tgyay0c!CRRrj)O|7OftyP0>Un$}Na9zK@ibk%= z_H~8tC6Sn39D3bjzt2~xt+ABuK$7Lk;5@%0oBa+W$rdgG%WA!CoeIn-_42yZ!C%jsl@Y(&zoIj|{c}?oXt47JICOGlG@oF7> zqoxM<)J)Cz2btdnkAla@F8Q=M$$v!oC7&Ks&XP}?{08AEc}PA@$)n`c2|{TjCTdA0 zviJi^-KO$;av0A`t3Q+oO8?1?2{u2}HSU8Q`dcifaf_%v>*lDGp0dUER@J~gMfN3VC+$?Rf{@f^j v$rrrAV5KCKc%_&?**rN