import { useEffect, useState } from 'react' import { Link } from 'react-router-dom' import { Sparkles, AlertTriangle, TrendingUp, Wrench, LineChart, Search, Boxes, CalendarClock, Cpu, ThumbsUp, ThumbsDown, Quote, } from 'lucide-react' import { Card, Btn, Field } from '../components/ui' import { getAiStatus, aiDefectRootCause, aiForecast, aiPredictiveMaintenance, aiSpcAnomaly, aiParseQuery, aiSafetyStock, aiScheduleOptimize, ragDefectAnalysis, ragPredictAnalysis, ragFeedback, postAiFeedback, } from '../api/client' /** 적용된 기법 배지 — 응답의 applied metadata 를 시각화(토글 effect 관측). */ function AppliedBadge({ data }: { data: any }) { const a = data?.applied if (!a) return null const chips: string[] = [`mode:${a.retrievalMode}`, `기법:${a.technique}`] if (a.rerank) chips.push('rerank') if (a.graphrag) chips.push('graphrag') if (a.hybrid) chips.push('hybrid') if (a.toolUse) chips.push(`agent(${a.maxSteps})`) if (a.structured) chips.push('structured') if (a.stream) chips.push('stream') if (data?.degraded) chips.push('⚠ degraded(폴백)') return (
{chips.map((c, i) => ( {c} ))}
) } /** 👍/👎 피드백 (중앙 /rag/feedback, solution=mes 격리). answerId 가 있을 때만 노출. */ function FeedbackBar({ answerId, query }: { answerId?: string; query?: string }) { const [done, setDone] = useState('') if (!answerId) return null const send = async (verdict: 'up' | 'down') => { try { await ragFeedback({ answerId, query, verdict }); setDone(verdict === 'up' ? '👍 반영됨' : '👎 반영됨') } catch { setDone('전송 실패') } } return (
이 답변이 도움이 됐나요? {done && {done}}
) } /** 👍/👎 로컬 학습 피드백 (POST /api/ai/feedback → 로컬 DuckDB + 중앙 rag). 결과가 있을 때만 노출. */ function LocalFeedbackBar({ feature, question, answer }: { feature: string; question?: string; answer?: any }) { const [done, setDone] = useState('') if (answer == null) return null const ans = typeof answer === 'string' ? answer : JSON.stringify(answer) const send = async (verdict: 'up' | 'down') => { try { await postAiFeedback({ feature, question, answer: ans, verdict }); setDone(verdict === 'up' ? '👍 반영됨' : '👎 반영됨') } catch { setDone('전송 실패') } } return (
이 결과가 도움이 됐나요? {done && {done}}
) } /** 인용 라벨 — 문서명·위치·근거지지도. citation/source 객체 형태 방어적 처리. */ function citeLabel(c: any): string { if (c == null) return '문서' if (typeof c === 'string') return c const src = c.source || c.document || c.doc || c.title || c.chunk_id || c.id || '문서' const loc = c.page != null ? ` p.${c.page}` : (c.location ? ` ${c.location}` : '') const sup = c.support != null ? ` · ${Math.round(Number(c.support) * 100)}%` : '' return `${src}${loc}${sup}` } /** * WISE 근거 UX — 인용 카드(sources/citations) + 환각차단(abstained) 배지. * node = 응답의 defect / interpretation 서브객체. 근거·보류 정보가 있을 때만 노출. */ function WiseEvidence({ node }: { node: any }) { if (node == null) return null const citations: any[] = Array.isArray(node.citations) ? node.citations : [] const sources: any[] = Array.isArray(node.sources) ? node.sources : [] const items = citations.length ? citations : sources const abstained = node.abstained === true if (!abstained && items.length === 0) return null return (
{abstained && (
근거가 부족해 답변을 보류했습니다 (오류 아님 · 안내)
)} {items.length > 0 ? ( <>
근거 인용 ({items.length})
{items.map((c, i) => ( {citeLabel(c)} ))}
) : (
근거 문서 없음
)}
) } function Output({ data }: { data: any }) { if (data == null) return null if (typeof data === 'string') return
{data}
return
{JSON.stringify(data, null, 2)}
} export default function AiTools() { const [ollama, setOllama] = useState(null) useEffect(() => { getAiStatus().then(d => setOllama(!!(d?.ollamaAvailable ?? d?.available))).catch(() => setOllama(false)) }, []) return (

WISE AI

Enterprise AI for Trusted Knowledge · 근거·인용·환각차단(중앙 guardia-rag) + 불량분석·생산예측·예지보전·SPC이상 (Ollama 온프레미스 + Java 폴백)

Ollama {ollama === null ? '확인 중' : ollama ? '온라인' : '오프라인 (Java 폴백)'}
{/* ── 최신 기법 (중앙 guardia-rag 경유) — 대표 2개 기능 ───────────── */}

WISE AI · 근거 기반 분석 (RAG · 에이전틱 · 구조화)

기법 토글 설정 →
{/* ── 기존 AI 도구 (불변, Ollama + Java 폴백) ───────────────────── */}

기본 AI 도구

) } /** 대표 1: 불량 원인분석 + SPC 이상감지 (SPC 수치=결정론, 원인서술=/rag/agent+structured). */ function RagDefectTool() { const [code, setCode] = useState(''); const [ctx, setCtx] = useState('') const [values, setValues] = useState(''); const [ucl, setUcl] = useState(''); const [lcl, setLcl] = useState(''); const [cl, setCl] = useState('') const [out, setOut] = useState(null); const [busy, setBusy] = useState(false) const run = async () => { setBusy(true) try { const vals = values.split(',').map(s => s.trim()).filter(Boolean).map(Number) const req: any = { defectCode: code, context: ctx ? [{ note: ctx }] : [] } if (vals.length) { req.values = vals; req.ucl = Number(ucl) || 0; req.lcl = Number(lcl) || 0; req.cl = Number(cl) || 0 } setOut(await ragDefectAnalysis(req)) } finally { setBusy(false) } } return ( }> setCode(e.target.value)} placeholder="예: D-DIM-01" />