♻️ refactor: AI 시세 분석을 네이버 쇼핑 최저가 조회로 교체
Some checks failed
ci / ci (22, ubuntu-latest) (push) Failing after 24m12s
Some checks failed
ci / ci (22, ubuntu-latest) (push) Failing after 24m12s
- server/api/ai/market-price: Anthropic 스트리밍 제거, 네이버 쇼핑 API 호출로 변경 - 신품 기준 최저가 10건 조회 (가격 오름차순, 중고·렌탈 제외) - NAVER_CLIENT_ID / NAVER_CLIENT_SECRET 환경변수 사용 - MarketPriceAnalysis: 스트리밍 텍스트 UI → 구조화된 가격 비교 UI - 신품 최저가 / 평균가 / 희망가 대비 비율 요약 카드 - 상품 목록 (이미지, 상품명, 쇼핑몰, 가격, 네이버 링크) - nuxt.config: naverClientId / naverClientSecret runtimeConfig 추가
This commit is contained in:
@@ -1,57 +1,61 @@
|
||||
import Anthropic from '@anthropic-ai/sdk'
|
||||
interface NaverShoppingItem {
|
||||
title: string
|
||||
link: string
|
||||
image: string
|
||||
lprice: string
|
||||
hprice: string
|
||||
mallName: string
|
||||
productId: string
|
||||
brand: string
|
||||
category1: string
|
||||
category2: string
|
||||
category3: string
|
||||
category4: string
|
||||
}
|
||||
|
||||
const SYSTEM_PROMPT = `당신은 한국 중고 캠핑 장비 시장 전문가입니다.
|
||||
사용자가 제공한 장비 정보를 바탕으로 중고 시세를 분석하고 판매 전략을 제안합니다.
|
||||
|
||||
분석 항목:
|
||||
1. **현재 시세 범위**: 해당 장비의 국내 중고 시장 시세 (최저~최고)
|
||||
2. **희망가 평가**: 제시된 희망가가 적정한지, 높은지, 낮은지 평가
|
||||
3. **추천 판매가**: 빠른 판매와 적정 수익을 고려한 추천 가격
|
||||
4. **판매 팁**: 플랫폼별 특성을 고려한 판매 전략 및 주의사항
|
||||
|
||||
응답 스타일:
|
||||
- 한국어로 간결하고 실용적으로 답변
|
||||
- 구체적인 가격대와 근거 제시
|
||||
- 마크다운 형식으로 구조화된 답변 제공`
|
||||
interface NaverShoppingResponse {
|
||||
lastBuildDate: string
|
||||
total: number
|
||||
start: number
|
||||
display: number
|
||||
items: NaverShoppingItem[]
|
||||
}
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const config = useRuntimeConfig()
|
||||
const body = await readBody(event)
|
||||
|
||||
const { itemName, platform, salePrice } = body as {
|
||||
itemName: string
|
||||
platform: string
|
||||
salePrice: number
|
||||
const { itemName } = body as { itemName: string; salePrice: number }
|
||||
|
||||
if (!itemName?.trim()) {
|
||||
throw createError({ statusCode: 400, message: '장비명이 필요합니다' })
|
||||
}
|
||||
|
||||
const anthropic = new Anthropic({ apiKey: config.anthropicApiKey })
|
||||
const clientId = config.naverClientId as string
|
||||
const clientSecret = config.naverClientSecret as string
|
||||
|
||||
const userMessage = `다음 캠핑 장비의 중고 시세를 분석해주세요.
|
||||
if (!clientId || !clientSecret) {
|
||||
throw createError({ statusCode: 500, message: '네이버 API 인증 정보가 설정되지 않았습니다' })
|
||||
}
|
||||
|
||||
- 장비명: ${itemName}
|
||||
- 판매 플랫폼: ${platform}
|
||||
- 희망 판매가: ${salePrice.toLocaleString('ko-KR')}원`
|
||||
const url = new URL('https://openapi.naver.com/v1/search/shop.json')
|
||||
url.searchParams.set('query', itemName)
|
||||
url.searchParams.set('display', '10')
|
||||
url.searchParams.set('sort', 'asc') // 가격 오름차순 (최저가 먼저)
|
||||
url.searchParams.set('exclude', 'used:rental') // 중고·렌탈 제외 → 신품 기준
|
||||
|
||||
const stream = anthropic.messages.stream({
|
||||
model: 'claude-sonnet-4-6',
|
||||
max_tokens: 1024,
|
||||
system: SYSTEM_PROMPT,
|
||||
messages: [{ role: 'user', content: userMessage }]
|
||||
})
|
||||
|
||||
const readableStream = new ReadableStream({
|
||||
async start(controller) {
|
||||
stream.on('text', (text) => {
|
||||
controller.enqueue(new TextEncoder().encode(text))
|
||||
})
|
||||
stream.on('finalMessage', () => {
|
||||
controller.close()
|
||||
})
|
||||
stream.on('error', (err) => {
|
||||
controller.error(err)
|
||||
})
|
||||
const response = await fetch(url.toString(), {
|
||||
headers: {
|
||||
'X-Naver-Client-Id': clientId,
|
||||
'X-Naver-Client-Secret': clientSecret
|
||||
}
|
||||
})
|
||||
|
||||
return sendStream(event, readableStream)
|
||||
if (!response.ok) {
|
||||
const text = await response.text()
|
||||
throw createError({ statusCode: response.status, message: `네이버 쇼핑 API 오류: ${text}` })
|
||||
}
|
||||
|
||||
const data = await response.json() as NaverShoppingResponse
|
||||
return data
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user