diff --git a/.env b/.env index 0b92af2..453d6a6 100644 --- a/.env +++ b/.env @@ -1,2 +1,3 @@ SECRET_KEY=bN3hZ6LbHG7nH9YXWULCr-crcS3GAaRELbNBdAyHBuiHH5TRctd0Zbd6OuLRHHa4Fbs -SENDER_PASSWORD=TXVU2unpCAE2EtEX \ No newline at end of file +SENDER_PASSWORD=TXVU2unpCAE2EtEX +KIMI_API_KEY=sk-icdiHIiv6x8XjJCaN6J6Un7uoVxm6df5WPhflq10ZVFo03D9 \ No newline at end of file diff --git a/app/api/v1/endpoints/aichat.py b/app/api/v1/endpoints/aichat.py new file mode 100644 index 0000000..f4748ff --- /dev/null +++ b/app/api/v1/endpoints/aichat.py @@ -0,0 +1,50 @@ +from fastapi import Depends, Request +from fastapi.responses import StreamingResponse +from app.utils.aichat import kimi_chat_stream +from app.utils.redis import get_redis_client +from app.utils.auth import get_current_user +import json +from fastapi import APIRouter +from app.schemas.aichat import NoteInput + + +router = APIRouter() +redis_client = get_redis_client() + +@router.post("/note", response_model=dict) +async def generate_notes( + input: NoteInput, + current_user: dict = Depends(get_current_user) +): + user_id = current_user["id"] + redis_key = f"aichat:{user_id}" + + # 1. 读取历史对话 + history = redis_client.get(redis_key) + if history: + messages = json.loads(history) + else: + # 首轮对话可加 system prompt + messages = [{"role": "system", "content": "你是一个智能笔记助手。"}] + + # 2. 追加用户输入 + messages.append({"role": "user", "content": input.input}) + + async def ai_stream(): + full_reply = "" + async for content in kimi_chat_stream(messages): + full_reply += content + yield f"data: {json.dumps({'content': content}, ensure_ascii=False)}\n\n" + messages.append({"role": "assistant", "content": full_reply}) + redis_client.set(redis_key, json.dumps(messages), ex=3600) + + return StreamingResponse(ai_stream(), media_type="text/event-stream") + +@router.get("/clear", response_model=dict) +async def clear_notes( + current_user: dict = Depends(get_current_user) +): + user_id = current_user["id"] + redis_key = f"aichat:{user_id}" + redis_client.delete(redis_key) + return {"msg": "clear successfully"} \ No newline at end of file diff --git a/app/core/config.py b/app/core/config.py index d89dbe7..5e13cf9 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -15,6 +15,7 @@ class Settings: SMTP_PORT: int = 465 # SMTP端口 SENDER_EMAIL : str = "jienote_buaa@163.com" SENDER_PASSWORD: str = os.getenv("SENDER_PASSWORD", "default_password") # 发件人邮箱密码 + KIMI_API_KEY: str = os.getenv("KIMI_API_KEY", "default_kimi_api_key") # KIMI API密钥 settings = Settings() \ No newline at end of file diff --git a/app/routers/router.py b/app/routers/router.py index 829a3b8..44409d0 100644 --- a/app/routers/router.py +++ b/app/routers/router.py @@ -3,6 +3,7 @@ from app.api.v1.endpoints.auth import router as auth_router from app.api.v1.endpoints.note import router as note_router from app.api.v1.endpoints.user import router as user_router +from app.api.v1.endpoints.aichat import router as aichat_router def include_auth_router(app): app.include_router(auth_router, prefix="/public", tags=["auth"]) @@ -13,7 +14,11 @@ def include_note_router(app): def include_user_router(app): app.include_router(user_router, prefix="/user", tags=["user"], dependencies=[Depends(get_current_user)]) +def include_aichat_router(app): + app.include_router(aichat_router, prefix="/chat", tags=["aichat"], dependencies=[Depends(get_current_user)]) + def include_routers(app): include_auth_router(app) include_note_router(app) - include_user_router(app) \ No newline at end of file + include_user_router(app) + include_aichat_router(app) \ No newline at end of file diff --git a/app/schemas/aichat.py b/app/schemas/aichat.py new file mode 100644 index 0000000..c6045c7 --- /dev/null +++ b/app/schemas/aichat.py @@ -0,0 +1,4 @@ +from pydantic import BaseModel + +class NoteInput(BaseModel): + input: str \ No newline at end of file diff --git a/app/utils/aichat.py b/app/utils/aichat.py new file mode 100644 index 0000000..c3e7f0c --- /dev/null +++ b/app/utils/aichat.py @@ -0,0 +1,24 @@ +from openai import AsyncOpenAI +from app.core.config import settings + +client = AsyncOpenAI( + api_key=settings.KIMI_API_KEY, + base_url="https://api.moonshot.cn/v1", +) + +async def kimi_chat_stream(messages, model="moonshot-v1-8k", temperature=0.3): + """ + 异步AI流式对话工具方法,传入消息列表,流式返回AI回复内容。 + :param messages: List[dict] + :yield: str,AI回复内容片段 + """ + stream = await client.chat.completions.create( + model=model, + messages=messages, + temperature=temperature, + stream=True + ) + async for chunk in stream: + content = getattr(chunk.choices[0].delta, "content", None) + if content: + yield content \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index e843fe8..78de939 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,10 +5,12 @@ anyio==4.9.0 async-timeout==5.0.1 asyncmy==0.2.10 bcrypt==4.3.0 +certifi==2025.1.31 cffi==1.17.1 click==8.1.8 colorama==0.4.6 cryptography==44.0.2 +distro==1.9.0 dnspython==2.7.0 dotenv==0.9.9 email_validator==2.2.0 @@ -16,11 +18,15 @@ fastapi==0.115.12 fastapi-pagination==0.12.34 greenlet==3.1.1 h11==0.14.0 +httpcore==1.0.8 +httpx==0.28.1 idna==3.10 +jiter==0.9.0 jwt==1.3.1 Mako==1.3.9 MarkupSafe==3.0.2 numpy==2.2.4 +openai==1.75.0 pandas==2.2.3 passlib==1.7.4 pycparser==2.22 @@ -37,6 +43,7 @@ six==1.17.0 sniffio==1.3.1 SQLAlchemy==2.0.40 starlette==0.46.1 +tqdm==4.67.1 typing-inspection==0.4.0 typing_extensions==4.13.1 tzdata==2025.2