import { useEffect, useState } from 'react' import Constants from 'expo-constants' /** * 기능 #11 — Ollama AI 자동 분류 훅 (온프레미스 sLLM, 외부 API 미사용) * 제목 입력 시 debounce 후 Ollama /api/generate 호출 → * { category, priority } JSON 파싱하여 입력 필드 자동 제안. */ const OLLAMA_HOST: string = Constants.expoConfig?.extra?.ollamaUrl ?? 'http://localhost:11434' const OLLAMA_MODEL: string = Constants.expoConfig?.extra?.ollamaModel ?? 'llama3' const CATEGORIES = ['DEPLOY', 'RESTART', 'LOG', 'INQUIRY', 'OTHER'] const PRIORITIES = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'] export interface AIClassifyResult { category: string priority: string loading: boolean error: boolean } function extractJson(text: string): { category?: string; priority?: string } | null { try { const match = text.match(/\{[\s\S]*\}/) if (!match) return null return JSON.parse(match[0]) } catch { return null } } export function useAIClassify(title: string): AIClassifyResult { const [category, setCategory] = useState('') const [priority, setPriority] = useState('') const [loading, setLoading] = useState(false) const [error, setError] = useState(false) useEffect(() => { const trimmed = title.trim() if (trimmed.length < 4) { setCategory(''); setPriority(''); setLoading(false); setError(false) return } let alive = true setLoading(true); setError(false) const controller = new AbortController() const timer = setTimeout(async () => { const prompt = `다음 SR 제목의 카테고리와 우선순위를 JSON으로만 답하라. ` + `category 는 [${CATEGORIES.join(', ')}] 중 하나, ` + `priority 는 [${PRIORITIES.join(', ')}] 중 하나. ` + `제목: "${trimmed}"` try { const res = await fetch(`${OLLAMA_HOST}/api/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: OLLAMA_MODEL, prompt, stream: false, format: 'json' }), signal: controller.signal, }) const data = await res.json() const parsed = extractJson(data?.response ?? '') if (!alive) return if (parsed) { const cat = (parsed.category ?? '').toUpperCase() const pri = (parsed.priority ?? '').toUpperCase() setCategory(CATEGORIES.includes(cat) ? cat : '') setPriority(PRIORITIES.includes(pri) ? pri : '') } else { setError(true) } } catch { if (alive) setError(true) } finally { if (alive) setLoading(false) } }, 700) return () => { alive = false; clearTimeout(timer); controller.abort() } }, [title]) return { category, priority, loading, error } }