From 82bba110eed8eebf976c77d92c055a7e0b847eb6 Mon Sep 17 00:00:00 2001 From: jeremygan2021 Date: Mon, 2 Mar 2026 12:32:45 +0800 Subject: [PATCH] =?UTF-8?q?AI=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 2 + .env.docker | 2 + .env.example | 4 + __pycache__/admin_routes.cpython-312.pyc | Bin 30677 -> 30677 bytes __pycache__/auth.cpython-312.pyc | Bin 4966 -> 4966 bytes __pycache__/config.cpython-312.pyc | Bin 1671 -> 1732 bytes __pycache__/database.cpython-312.pyc | Bin 3935 -> 3935 bytes __pycache__/image_processor.cpython-312.pyc | Bin 5679 -> 5679 bytes __pycache__/main.cpython-312.pyc | Bin 5073 -> 5073 bytes __pycache__/models.cpython-312.pyc | Bin 328 -> 328 bytes __pycache__/mqtt_manager.cpython-312.pyc | Bin 10828 -> 10828 bytes __pycache__/schemas.cpython-312.pyc | Bin 6949 -> 6949 bytes api/__init__.py | 5 +- api/__pycache__/__init__.cpython-312.pyc | Bin 616 -> 749 bytes api/__pycache__/__init__.cpython-313.pyc | Bin 598 -> 725 bytes api/__pycache__/ai.cpython-312.pyc | Bin 0 -> 5970 bytes api/__pycache__/ai_schemas.cpython-312.pyc | Bin 0 -> 1823 bytes api/__pycache__/contents.cpython-312.pyc | Bin 24282 -> 24282 bytes api/__pycache__/devices.cpython-312.pyc | Bin 7833 -> 7833 bytes api/__pycache__/devices.cpython-313.pyc | Bin 7782 -> 7782 bytes api/__pycache__/todos.cpython-312.pyc | Bin 7658 -> 7658 bytes api/ai.py | 157 ++++++++++++++++++++ api/ai_schemas.py | 30 ++++ api/prompts.py | 31 ++++ config.py | 3 + 25 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 api/__pycache__/ai.cpython-312.pyc create mode 100644 api/__pycache__/ai_schemas.cpython-312.pyc create mode 100644 api/ai.py create mode 100644 api/ai_schemas.py create mode 100644 api/prompts.py diff --git a/.env b/.env index 64c61ee..a270a7a 100644 --- a/.env +++ b/.env @@ -4,6 +4,8 @@ # DATABASE_URL=postgresql://luna:123luna@121.43.104.161:6432/luna DATABASE_URL=postgresql://luna:123luna@6.6.6.66:5432/luna +DASHSCOPE_API_KEY=sk-657968d48d0249099f3809f796f80a4f + # MQTT配置 MQTT_BROKER_HOST=luna-mqtt MQTT_BROKER_PORT=1883 diff --git a/.env.docker b/.env.docker index a81650a..3bed271 100644 --- a/.env.docker +++ b/.env.docker @@ -9,6 +9,8 @@ MQTT_BROKER_PORT=1883 MQTT_USERNAME=luna2025 MQTT_PASSWORD=123luna2021 +DASHSCOPE_API_KEY=sk-657968d48d0249099f3809f796f80a4f + # 应用配置 APP_NAME=墨水屏桌面屏幕系统 DEBUG=false diff --git a/.env.example b/.env.example index a81650a..f051f8e 100644 --- a/.env.example +++ b/.env.example @@ -9,6 +9,10 @@ MQTT_BROKER_PORT=1883 MQTT_USERNAME=luna2025 MQTT_PASSWORD=123luna2021 +DASHSCOPE_API_KEY=sk-657968d48d0249099f3809f796f80a4f + + + # 应用配置 APP_NAME=墨水屏桌面屏幕系统 DEBUG=false diff --git a/__pycache__/admin_routes.cpython-312.pyc b/__pycache__/admin_routes.cpython-312.pyc index 362630acff7796e081b0b7607d6921517fee5236..315aa7848082f36692baefa4ac9cf88744bdbaa5 100644 GIT binary patch delta 22 ccmccmp7H8?M()$Ryj%=G(8a%WBloFt0AY3qzyJUM delta 22 ccmccmp7H8?M()$Ryj%=Gptn$RBloFt0AHd9Y5)KL diff --git a/__pycache__/auth.cpython-312.pyc b/__pycache__/auth.cpython-312.pyc index 97b279b1330c8d2cfdf1162791994ba40a83653e..ca413ccadae506c402e3af59c8b1a408f4e8d049 100644 GIT binary patch delta 20 acmaE+_DqfYG%qg~0}yoaFWtx;Eerrb0|k2k delta 20 acmaE+_DqfYG%qg~0}#Bqr?!zhS{MLE5C$y( diff --git a/__pycache__/config.cpython-312.pyc b/__pycache__/config.cpython-312.pyc index 7fecc2ae5d0b3daeaba116de7718d3115d12ec9a..8035ce464363e42fb512c01b84eed309c01b318b 100644 GIT binary patch delta 311 zcmZqYJ;KX-nwOW00SN93F3p@ak#`!S!Nw(=j5;aoQIe@#sVtH}sZ`b!B^w~k#Nf`5 zqTIreqOzI^q=kVYN~)4wQ+4xE#=VTfw**rXi!+Lo^9xer6ALoqvr{W4H!)u{Q~;{e z1ma>IAko5bLs+tdCzsQJPGq&@~QGVq9WTQzck-2ng) CphfZk delta 233 zcmX@Y+s?~-nwOW00SMf=mu3b{Xk3=%F<1rcCX>L3;y zj9>vWiZ4tKVm-<8orQ^2{u2X`C{mkj!RFwDs^=DmO>TZlX-=wLkpWN>BS<62mJiH~ kjEr{~wC*xUe_>H!l<%nd!T_W``YwKG%qg~0}yoaFWty}Oc($@-vzz^ delta 20 acmcbpeo>wKG%qg~0}$vfl-$UDOc($?3k7HZ diff --git a/__pycache__/models.cpython-312.pyc b/__pycache__/models.cpython-312.pyc index 1a9dd5c195d26232bd8c42ad84095530f96e52ac..39d8aebb4e284558a164573391a81e0f42834f5d 100644 GIT binary patch delta 20 acmX@Xbb^WdG%qg~0}yoaFWty(%Lo8BF$Bi| delta 20 acmX@Xbb^WdG%qg~0}$vfl-$T|%Lo89T?BCe diff --git a/__pycache__/mqtt_manager.cpython-312.pyc b/__pycache__/mqtt_manager.cpython-312.pyc index 57157cf7ea7a3a5c1baeb32f8f4f5a1013a6979a..081f250e44bb101739254ccb2131f5fbd99805c8 100644 GIT binary patch delta 20 acmX>TawdfPG%qg~0}yoaFWt!Ps09E+f(4)e delta 20 acmX>TawdfPG%qg~0}$vfl-$Ves09E)t_4Z} diff --git a/__pycache__/schemas.cpython-312.pyc b/__pycache__/schemas.cpython-312.pyc index aee2958e3d7ec7c75012a5e27f89c8927c2faf1e..20066c589fe3a3b8c424db3e57583888624fedf2 100644 GIT binary patch delta 20 acmZ2#w$zOKG%qg~0}yoaFWty3B@F;MBLv_8 delta 20 acmZ2#w$zOKG%qg~0}$vfl-$TIB@F;KPXvkp diff --git a/api/__init__.py b/api/__init__.py index ed9a309..b307303 100644 --- a/api/__init__.py +++ b/api/__init__.py @@ -1,9 +1,10 @@ from fastapi import APIRouter -from api import devices, contents, todos +from api import devices, contents, todos, ai api_router = APIRouter() # 注册所有路由,并添加全局安全要求 api_router.include_router(devices.router, prefix="/devices") api_router.include_router(contents.router, prefix="/contents") -api_router.include_router(todos.router, prefix="/todos") \ No newline at end of file +api_router.include_router(todos.router, prefix="/todos") +api_router.include_router(ai.router, prefix="/ai", tags=["AI生成"]) \ No newline at end of file diff --git a/api/__pycache__/__init__.cpython-312.pyc b/api/__pycache__/__init__.cpython-312.pyc index 3b7591f3a017eddc498ad55f8e9549ec662b6bff..329c9f5576976465ad8c302c32cd73caf2902bdc 100644 GIT binary patch delta 370 zcmaFC@|IQOG%qg~0}${DEzO+F$iVOz#DM`;DC4sd<3tT5y}hE&GYP>Cq^ zRQ4=Em~sT2%A6%Mu|!c4yCe%xavd&7R-ohyg)kiEvSkS&Y=N;-*i$&t8P{;GW`>9` zGE{PDa=!#|G#PJkItF+K<(HPE7HP6fR$!E3xy6*2IoXO)U8#y$KQXhE1E|c=^ZAtd z&pIY(G8KWiw^&LN(~JEyxhAJDDlrwYPi|%`mj+o33?PQ$av<@6nURt4E`#iS2A#_c uI+I12yp^voSlnRXy3QbZkwJ2X(tORCnk!s)NbTUcz#w^*!L>*Ls0#pXC_p9v delta 289 zcmaFM`hrE{G%qg~0}$vfl+5&GWMFs<;=lk4l=1li!$b`wEk*_=hE&GYP{Am+RJJUB zm{J6t%A6%Iu|hEzyCh4N075B@mBPA)Wi=Co$H;)gSXQ91ZxoCw*)=&{f|P19-r{r& z@C?c?ElDlXWWL3ol3JFToLYQ~BRM~>BsH(3_!etPeoB7v^2 diff --git a/api/__pycache__/__init__.cpython-313.pyc b/api/__pycache__/__init__.cpython-313.pyc index f2332b04af1976aa376de1e477397d793db87fee..528f0fd2b904cb183f1ce3113bc71c1ae44da9d8 100644 GIT binary patch delta 411 zcmcb{a+Ou%GcPX}0}${DEzO+F$iVOz#DM`eDC091<3tUmdM~yjHU)+lMkNMQs6-Ka z4116uOgVy%VGa^P@L;T9=3o{}CZGw}^{`;o!wT0Uj?E(0AfbtOqylgl$%fTP_FxWu z#$ZlMW(9_HE=}&2Aopo9-r{r&@C?c?ElDlXWSPvzD8+J%DKT@h2BW%C6|;U~W+?|y znWN|PDf6FoOweR10&#D#lq9AX`)P7b4q{YdDq^2p$XG57@;NXh8H&q+#0O?ZM#j4g zviBKuE;Hy%W@hqMzQ|y4gMsThgXBd9$r(!XHD_wBaNQxbgXaQ+Xk28_ Rn4He!&2fprqKFSD1ptNsCD8x? diff --git a/api/__pycache__/ai.cpython-312.pyc b/api/__pycache__/ai.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c09ad35d70c5d90a7e6476d9fc905a2656f1b486 GIT binary patch literal 5970 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 ZWEBfRXSnGbguhs0Cvit=2zkH#owjc!Ns`Ct-&~ zGL{Vi8-%qaG(`T|%8+cO#e=uYb2N?p43} zb@9sO#jh_eT=N#czx#Ol>TRBd4MNkl>C?lMyq(MDoe{Sgf~wc9R=tTeU)F4qF z(Q01jgMEAY_jMt-5%l+$ug=`9PR}xln&-(94{G05+rYwCGfS7RE#c|nqKz?37$Z!` z5XM{QLm%r_fA4|aPQU5rfAq_5i`OS{yOX!7-+syDEZ%86%2Z9W^eoXd7Sc3Ox*3!s zns(OJGc}8iIqWEA5UFFcW=+#AE9dysP))O&(2nJ5<5BJBd{5JI>~$dB=9U4ue+CjK zDY0qdBVvFz8Q0SL_w?^i=y@}tn;K1@CRv^K<_k79 ziVv8GegJTZn{V4t67R={Ds9Q)k@2Cjl$`JEn%sB)&`70ov^eUWDy1jhD@&vEE$h59 zlb=>v28zk?j9x4tNX<0fn-@4xW7N&V`fNDKLUyfE!4ePJ>v8uR5U_4**SHNO>?L~>!V3ug)on(}KkrtQ+E)Q9 zi|zsdx?>wlvB|DVY=80S_*-RZ|9pGrO=C7xX&)-S>ZzsJM0AZbjDI$1pM#~5G$Ino z;IZ+1NkGXEQ}7@V(QkUG_Ar1h(4dp+ek$mK5E=SVK<|2lQny0OKf~%jA_Wnsj;vAb zW*i&IVG4!HcIgO{($FI;kR_Dr&{;~(b@HymWEZFL$bad5Lh24!rbg;cl#o0-fPnXC z#}PIoJYjn~P7zw*N8bjx#LY!pE*zVSG+%gaZqrt%+g;w?E4$0m*10X)fc{fn`U;fx zwz-|%Q2MWUCrTY9TG}=7{)5Q&veZ4dqYG;IMK4*3mC~h+6C)489TlmomJ$<<#KTO@ z7YyBU%(RXF6MlU##VO=XYpk|?e8L-c?zdn&>iqpaeFml;tUVtCb`QAVYef$L{3Y@{ s|B&l^$n})Do)tyrlYA+@!r^zNNdWZTS>f=zqAGm1_tM`SeEg063*z6(`v3p{ literal 0 HcmV?d00001 diff --git a/api/__pycache__/contents.cpython-312.pyc b/api/__pycache__/contents.cpython-312.pyc index 34afc9c11542f2a017ccfddef53221339745af5e..5495af137d9697001df1bd78aa552aad3c19fbb3 100644 GIT binary patch delta 22 ccmcb$m+{tKM()$Ryj%=G(8a%WBlr0@09uv@QUCw| delta 22 ccmcb$m+{tKM()$Ryj%=Gknvk>Blr0@09>^Pod5s; diff --git a/api/__pycache__/devices.cpython-312.pyc b/api/__pycache__/devices.cpython-312.pyc index d8710b06ab551de811e0cf6895f3d5a25f5700eb..b2492fd573026e65af2c6b696bb72ca56d3ed848 100644 GIT binary patch delta 20 acmbPfJJXi?G%qg~0}yoaFWt!9CkFsILIq6# delta 20 acmbPfJJXi?G%qg~0}$vfl-$VOCkFsGZUpxL diff --git a/api/__pycache__/devices.cpython-313.pyc b/api/__pycache__/devices.cpython-313.pyc index 031311c2eeb912e1b8a154873c5c3ba688c0cda2..a6b3d0f5ae68047e0e5763f4aadea77f1d94c23d 100644 GIT binary patch delta 20 acmaE6^UQ|(GcPX}0}yoaFWtx;Ee8Nbeg)b9 delta 20 acmaE6^UQ|(GcPX}0}$vfl-$T2Ee8NZss)4q diff --git a/api/__pycache__/todos.cpython-312.pyc b/api/__pycache__/todos.cpython-312.pyc index 4a612e559b2fe811ffd98cd5cac64c306dca943d..5a068548525a31af9ed832224342ca72b00a987e 100644 GIT binary patch delta 20 acmaE5{mPpAG%qg~0}yoaFWty}UlssGp$0nu delta 20 acmaE5{mPpAG%qg~0}$vfl-$UDUlssE%?0HE diff --git a/api/ai.py b/api/ai.py new file mode 100644 index 0000000..4da2ca6 --- /dev/null +++ b/api/ai.py @@ -0,0 +1,157 @@ +from fastapi import APIRouter, HTTPException, Depends +from typing import Dict, Any +import httpx +import json +import logging +from config import settings +from api.ai_schemas import AIGenerationRequest, AITaskResponse, AITaskResult + +router = APIRouter() +logger = logging.getLogger(__name__) + +DASHSCOPE_API_URL = "https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation" +DASHSCOPE_TASK_URL = "https://dashscope.aliyuncs.com/api/v1/tasks" + +@router.post("/generate", response_model=AITaskResponse, summary="提交AI图片生成任务") +async def generate_image(request: AIGenerationRequest): + """ + 提交AI图片生成任务,使用阿里云DashScope服务 + """ + if not settings.dashscope_api_key: + raise HTTPException(status_code=500, detail="DashScope API Key not configured") + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {settings.dashscope_api_key}", + "X-DashScope-Async": "enable" # 确保异步任务提交 + } + + # 构建请求体 + payload = { + "model": request.model, + "input": { + "messages": [ + { + "role": "user", + "content": [ + { + "text": request.prompt + } + ] + } + ] + }, + "parameters": { + "prompt_extend": True, + "watermark": False, + "n": request.n, + "size": request.size + } + } + + if request.negative_prompt: + payload["parameters"]["negative_prompt"] = request.negative_prompt + + try: + async with httpx.AsyncClient() as client: + response = await client.post( + DASHSCOPE_API_URL, + headers=headers, + json=payload, + timeout=30.0 + ) + + if response.status_code != 200: + logger.error(f"DashScope API error: {response.text}") + try: + error_detail = response.json() + except: + error_detail = {"message": response.text} + raise HTTPException(status_code=response.status_code, detail=error_detail) + + result = response.json() + # 检查是否有task_id,因为如果是同步返回可能没有task_id,但在这种模型通常是异步的 + # 这里的curl示例似乎是直接返回task_id + + if "output" in result and "task_id" in result["output"]: + task_id = result["output"]["task_id"] + elif "task_id" in result: # 或者是这种结构 + task_id = result["task_id"] + else: + # 有些情况下直接返回 output.task_id + # 根据文档 https://help.aliyun.com/zh/dashscope/developer-reference/api-details-10 + # 异步提交返回结构通常包含 output.task_id + if "output" in result and "task_id" in result["output"]: + task_id = result["output"]["task_id"] + else: + # 如果是同步返回,可能直接给结果,或者结构不同 + # 但 wan2.6-t2i 通常是异步任务 + logger.warning(f"Unexpected response structure: {result}") + # 尝试直接取,或者抛错 + # 假设是标准异步结构 + task_id = result.get("output", {}).get("task_id") + + if not task_id: + raise HTTPException(status_code=500, detail="Failed to retrieve task_id from DashScope response") + + return AITaskResponse( + task_id=task_id, + request_id=result.get("request_id") + ) + + except httpx.RequestError as e: + logger.error(f"Request error: {str(e)}") + raise HTTPException(status_code=500, detail=f"Request failed: {str(e)}") + +@router.get("/tasks/{task_id}", response_model=AITaskResult, summary="查询AI任务结果") +async def get_task_result(task_id: str): + """ + 查询AI生成任务的结果 + """ + if not settings.dashscope_api_key: + raise HTTPException(status_code=500, detail="DashScope API Key not configured") + + headers = { + "Authorization": f"Bearer {settings.dashscope_api_key}" + } + + try: + async with httpx.AsyncClient() as client: + response = await client.get( + f"{DASHSCOPE_TASK_URL}/{task_id}", + headers=headers, + timeout=30.0 + ) + + if response.status_code != 200: + logger.error(f"DashScope Task API error: {response.text}") + raise HTTPException(status_code=response.status_code, detail="Failed to fetch task status") + + result = response.json() + + # 构建返回结果 + # DashScope 任务查询返回结构: + # { + # "request_id": "...", + # "output": { + # "task_id": "...", + # "task_status": "SUCCEEDED", + # "results": [...] + # }, + # "usage": ... + # } + + task_status = result.get("output", {}).get("task_status", "UNKNOWN") + + return AITaskResult( + task_id=task_id, + status=task_status, + code=result.get("code"), + message=result.get("message"), + output=result.get("output"), + usage=result.get("usage") + ) + + except httpx.RequestError as e: + logger.error(f"Request error: {str(e)}") + raise HTTPException(status_code=500, detail=f"Request failed: {str(e)}") diff --git a/api/ai_schemas.py b/api/ai_schemas.py new file mode 100644 index 0000000..18a75e6 --- /dev/null +++ b/api/ai_schemas.py @@ -0,0 +1,30 @@ +from pydantic import BaseModel, Field +from typing import Optional, List, Dict, Any + +# AI生成相关模型 +class AIGenerationRequest(BaseModel): + prompt: str = Field(..., description="生成图片的提示词") + negative_prompt: Optional[str] = Field(None, description="反向提示词") + size: str = Field("1024*1024", description="图片尺寸") + n: int = Field(1, description="生成数量", ge=1, le=4) + model: str = Field("wan2.6-t2i", description="使用的模型") + +class AITemplateGenerationRequest(BaseModel): + template_id: str = Field(..., description="提示词模板ID") + params: Dict[str, str] = Field(default_factory=dict, description="提示词参数") + negative_prompt: Optional[str] = Field(None, description="反向提示词") + size: str = Field("1024*1024", description="图片尺寸") + n: int = Field(1, description="生成数量", ge=1, le=4) + model: str = Field("wan2.6-t2i", description="使用的模型") + +class AITaskResponse(BaseModel): + task_id: str = Field(..., description="任务ID") + request_id: Optional[str] = Field(None, description="请求ID") + +class AITaskResult(BaseModel): + task_id: str + status: str + code: Optional[str] = None + message: Optional[str] = None + output: Optional[Dict[str, Any]] = None + usage: Optional[Dict[str, Any]] = None diff --git a/api/prompts.py b/api/prompts.py new file mode 100644 index 0000000..9fbf90a --- /dev/null +++ b/api/prompts.py @@ -0,0 +1,31 @@ +# 预设提示词模板库 +# 使用Python的format语法进行参数替换,例如 {keyword} + +PROMPTS = { + "flower_shop": "一间有着精致窗户的花店,漂亮的木质门,摆放着{flower_type},风格为{style}", + "landscape": "宏伟的{season}自然风景,包含{element},光线为{lighting},高分辨率,写实风格", + "cyberpunk_city": "赛博朋克风格的未来城市,霓虹灯闪烁,{weather}天气,街道上有{vehicle}", + "portrait": "一张{gender}的肖像照,{expression}表情,背景是{background},专业摄影布光", + "default": "一间有着精致窗户的花店,漂亮的木质门,摆放着花朵" +} + +def get_prompt(template_id: str, **kwargs) -> str: + """ + 获取并格式化提示词 + :param template_id: 提示词模板ID + :param kwargs: 替换参数 + :return: 格式化后的提示词 + """ + template = PROMPTS.get(template_id) + if not template: + return None + + try: + # 使用 safe_substitute 避免参数缺失报错? + # Python的format如果缺参数会报错,这里直接用format,让调用者负责提供完整参数 + # 或者我们可以在这里处理默认值 + return template.format(**kwargs) + except KeyError as e: + # 如果缺少参数,抛出异常或返回包含未替换占位符的字符串 + # 这里为了简单,如果出错,我们尽量保留原样或者报错 + raise ValueError(f"缺少提示词参数: {e}") diff --git a/config.py b/config.py index c9600d8..1177cf5 100644 --- a/config.py +++ b/config.py @@ -34,6 +34,9 @@ class Settings(BaseSettings): # 管理员配置 admin_username: str = "admin" admin_password: str = "123456" + + # DashScope配置 + dashscope_api_key: Optional[str] = None class Config: env_file = ".env"