151 lines
4.9 KiB
Python
151 lines
4.9 KiB
Python
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
|