Files
AIcreat/app/main.py
2026-04-07 18:55:50 +08:00

151 lines
4.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
import logging
from urllib.parse import urlparse
from fastapi import FastAPI, File, Request, UploadFile
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from app.config import settings
from app.logging_setup import configure_logging
from app.middleware import RequestContextMiddleware
from app.schemas import IMPublishRequest, RewriteRequest, WechatPublishRequest
from app.services.ai_rewriter import AIRewriter
from app.services.im import IMPublisher
from app.services.wechat import WechatPublisher
configure_logging()
logger = logging.getLogger(__name__)
app = FastAPI(title=settings.app_name)
@app.on_event("startup")
async def _log_startup() -> None:
logger.info(
"app_start name=%s openai_configured=%s ai_soft_accept=%s",
settings.app_name,
bool(settings.openai_api_key),
settings.ai_soft_accept,
)
app.add_middleware(RequestContextMiddleware)
app.mount("/static", StaticFiles(directory="app/static"), name="static")
templates = Jinja2Templates(directory="app/templates")
rewriter = AIRewriter()
wechat = WechatPublisher()
im = IMPublisher()
@app.get("/", response_class=HTMLResponse)
async def index(request: Request):
return templates.TemplateResponse("index.html", {"request": request, "app_name": settings.app_name})
@app.get("/favicon.ico", include_in_schema=False)
async def favicon():
# 浏览器通常请求 /favicon.ico统一跳转到静态图标
return RedirectResponse(url="/static/favicon.svg?v=20260406a")
@app.get("/api/config")
async def api_config():
"""供页面展示:当前是否接入模型、模型名、提供方(不含密钥)。"""
base = settings.openai_base_url or ""
provider = "dashscope" if "dashscope.aliyuncs.com" in base else "openai_compatible"
host = urlparse(base).netloc if base else ""
return {
"openai_configured": bool(settings.openai_api_key),
"openai_model": settings.openai_model,
"provider": provider,
"base_url_host": host or None,
"openai_timeout_sec": settings.openai_timeout,
"openai_max_output_tokens": settings.openai_max_output_tokens,
}
@app.post("/api/rewrite")
async def rewrite(req: RewriteRequest, request: Request):
rid = getattr(request.state, "request_id", "")
src = req.source_text or ""
logger.info(
"api_rewrite_in rid=%s source_chars=%d title_hint_chars=%d tone=%s audience=%s "
"keep_points_chars=%d avoid_words_chars=%d",
rid,
len(src),
len(req.title_hint or ""),
req.tone,
req.audience,
len(req.keep_points or ""),
len(req.avoid_words or ""),
)
result = rewriter.rewrite(req, request_id=rid)
tr = result.trace or {}
logger.info(
"api_rewrite_out rid=%s mode=%s duration_ms=%s quality_notes=%d trace_steps=%s soft_accept=%s",
rid,
result.mode,
tr.get("duration_ms"),
len(result.quality_notes or []),
len((tr.get("steps") or [])),
tr.get("quality_soft_accept"),
)
return result
@app.post("/api/publish/wechat")
async def publish_wechat(req: WechatPublishRequest, request: Request):
rid = getattr(request.state, "request_id", "")
logger.info(
"api_wechat_in rid=%s title_chars=%d summary_chars=%d body_md_chars=%d author_set=%s",
rid,
len(req.title or ""),
len(req.summary or ""),
len(req.body_markdown or ""),
bool((req.author or "").strip()),
)
out = await wechat.publish_draft(req, request_id=rid)
wcode = (out.data or {}).get("errcode") if isinstance(out.data, dict) else None
logger.info(
"api_wechat_out rid=%s ok=%s wechat_errcode=%s detail_preview=%s",
rid,
out.ok,
wcode,
(out.detail or "")[:240],
)
return out
@app.post("/api/wechat/cover/upload")
async def upload_wechat_cover(request: Request, file: UploadFile = File(...)):
rid = getattr(request.state, "request_id", "")
fn = file.filename or "cover.jpg"
content = await file.read()
logger.info("api_wechat_cover_upload_in rid=%s filename=%s bytes=%d", rid, fn, len(content))
out = await wechat.upload_cover(fn, content, request_id=rid)
logger.info(
"api_wechat_cover_upload_out rid=%s ok=%s detail=%s",
rid,
out.ok,
(out.detail or "")[:160],
)
return out
@app.post("/api/publish/im")
async def publish_im(req: IMPublishRequest, request: Request):
rid = getattr(request.state, "request_id", "")
logger.info(
"api_im_in rid=%s title_chars=%d body_md_chars=%d",
rid,
len(req.title or ""),
len(req.body_markdown or ""),
)
out = await im.publish(req, request_id=rid)
logger.info("api_im_out rid=%s ok=%s detail=%s", rid, out.ok, (out.detail or "")[:120])
return out