siteagent / services.py
ginigen-ai's picture
Update services.py
d9472c7 verified
import os, json, re
from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse
from shared import GROQ_API_KEY, NAVER_CLIENT_ID, NAVER_CLIENT_SECRET, _sanitize_text
router = APIRouter()
@router.post("/api/writer")
async def writer_endpoint(request: Request):
if not GROQ_API_KEY:
return JSONResponse({"error": "GROQ_API_KEY not set"}, status_code=500)
try:
body = await request.json()
except:
return JSONResponse({"error": "invalid json"}, status_code=400)
style = (body.get("style") or "블로그")[:20]
topic = _sanitize_text(body.get("topic") or "")[:1000]
context = _sanitize_text(body.get("context") or "")[:3000]
tone = (body.get("tone") or "전문적")[:20]
if not topic and not context:
return JSONResponse({"error": "주제 또는 참고 내용이 필요합니다"}, status_code=400)
style_guides = {
"블로그": "SEO에 적합한 블로그 포스트. 제목(H1) + 소제목(H2) 구조. 도입-본문-마무리. 키워드 자연스럽게 배치.",
"SNS": "인스타그램/페이스북용 짧고 임팩트 있는 글. 이모지 활용. 해시태그 5~10개 포함. 300자 이내.",
"이메일": "비즈니스 이메일. 제목줄 + 인사 + 본문 + 마무리. 정중하고 간결하게.",
"보도자료": "언론 보도자료 형식. 제목 + 부제 + 리드문(누가/언제/어디서/무엇을/왜) + 본문 + 회사소개.",
"광고카피": "짧고 강렬한 광고 카피. 헤드라인 + 서브카피 + CTA. 다양한 버전 3개 제시.",
"유튜브대본": "유튜브 영상 대본. 후킹 오프닝 + 본문(타임스탬프 포함) + 아웃트로 + CTA.",
}
guide = style_guides.get(style, style_guides["블로그"])
prompt = f"""다음 조건으로 글을 작성해줘:
- 스타일: {style}
- 톤: {tone}
- 작성 가이드: {guide}
"""
if topic:
prompt += f"\n- 주제: {topic}\n"
if context:
prompt += f"\n- 참고 내용:\n{context}\n"
prompt += "\n반드시 한국어로 작성. 바로 사용할 수 있는 완성된 글로 출력."
import httpx
try:
async with httpx.AsyncClient(timeout=60.0) as client:
resp = await client.post(
"https://api.groq.com/openai/v1/chat/completions",
headers={"Authorization": f"Bearer {GROQ_API_KEY}", "Content-Type": "application/json"},
json={"model": "openai/gpt-oss-120b", "messages": [{"role": "user", "content": prompt}],
"max_completion_tokens": 3000, "temperature": 0.8}
)
if resp.status_code != 200:
return JSONResponse({"error": f"API error {resp.status_code}"}, status_code=502)
rd = resp.json()
text = rd.get("choices", [{}])[0].get("message", {}).get("content", "글을 생성하지 못했습니다.")
return {"ok": True, "content": text, "style": style}
except Exception as e:
return JSONResponse({"error": str(e)[:200]}, status_code=500)
@router.post("/api/shopping")
async def shopping_endpoint(request: Request):
if not NAVER_CLIENT_ID or not NAVER_CLIENT_SECRET:
return JSONResponse({"error": "NAVER API 키가 설정되지 않았습니다"}, status_code=500)
try:
body = await request.json()
except:
return JSONResponse({"error": "invalid json"}, status_code=400)
query = _sanitize_text(body.get("query") or "")[:100]
sort = body.get("sort", "sim")
if not query:
return JSONResponse({"error": "검색어가 필요합니다"}, status_code=400)
import httpx
try:
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.get(
"https://openapi.naver.com/v1/search/shop.json",
params={"query": query, "display": 20, "sort": sort},
headers={
"X-Naver-Client-Id": NAVER_CLIENT_ID,
"X-Naver-Client-Secret": NAVER_CLIENT_SECRET,
}
)
if resp.status_code != 200:
print(f"[shopping] Naver API {resp.status_code}: {resp.text[:200]}")
return JSONResponse({"error": f"네이버 API 오류 ({resp.status_code})"}, status_code=502)
data = resp.json()
items = data.get("items", [])
if not items:
return {"ok": True, "items": [], "total": 0, "query": query, "tip": "검색 결과가 없습니다."}
results = []
for item in items[:20]:
title_clean = re.sub(r'</?b>', '', item.get("title", ""))
results.append({
"title": title_clean,
"price": int(item.get("lprice", 0)),
"hprice": int(item.get("hprice", 0)) if item.get("hprice") else None,
"mall": item.get("mallName", ""),
"link": item.get("link", ""),
"image": item.get("image", ""),
"category": "/".join(filter(None, [
item.get("category1", ""),
item.get("category2", ""),
item.get("category3", ""),
])),
"type": "price_compare" if item.get("productType") == "2" else "single",
})
results.sort(key=lambda x: x["price"] if x["price"] > 0 else 99999999)
lowest = results[0]["price"] if results else 0
highest = max((r["price"] for r in results), default=0)
return {
"ok": True,
"items": results,
"total": data.get("total", len(results)),
"query": query,
"lowest": lowest,
"highest": highest,
"sort": sort,
}
except Exception as e:
print(f"[shopping] error: {e}")
return JSONResponse({"error": str(e)[:200]}, status_code=500)