guardia-messenger/components/NextActions.tsx

80 lines
2.8 KiB
TypeScript

/**
* NextActions (#20) — 다음 명령 3개 제안
*
* 현재 컨텍스트(SR 상태/서버 상태 등)를 Ollama에 전달 → 다음 행동 3가지 JSON 배열.
* 버튼 3개로 표시, 탭하면 onSelect 콜백 호출.
*
* 보안: 컨텍스트에 자격증명/IP 포함 금지 (호출 측 책임). 온프레미스 Ollama만 사용.
*/
import { useState, useEffect } from 'react'
import { View, Text, Pressable, StyleSheet, ActivityIndicator } from 'react-native'
import { COLORS } from '../constants/Config'
import { generateJSON, DEFAULT_TEXT_MODEL } from '../lib/ollama'
interface Props {
context: string
onSelect: (action: string) => void
}
const FALLBACK: string[] = ['KB 조회', '담당자에게 연락', '에스컬레이션']
export function NextActions({ context, onSelect }: Props) {
const [actions, setActions] = useState<string[]>([])
const [loading, setLoading] = useState(false)
useEffect(() => {
let alive = true
;(async () => {
if (!context?.trim()) {
setActions(FALLBACK)
return
}
setLoading(true)
const prompt =
`당신은 IT 운영 어시스턴트입니다. 운영자가 다음 상황에 있습니다: "${context}". ` +
`다음으로 취할 행동 3가지를 한국어 짧은 문구로, 설명 없이 JSON 배열만 출력하세요. ` +
`예: ["에스컬레이션","KB 조회","담당자 연락"]`
const result = await generateJSON<string[]>(DEFAULT_TEXT_MODEL, prompt, FALLBACK)
if (!alive) return
const clean = Array.isArray(result) ? result.filter(x => typeof x === 'string').slice(0, 3) : FALLBACK
setActions(clean.length ? clean : FALLBACK)
setLoading(false)
})()
return () => {
alive = false
}
}, [context])
return (
<View style={S.wrap}>
<Text style={S.title}> </Text>
{loading ? (
<ActivityIndicator color={COLORS.accent} style={{ marginVertical: 8 }} />
) : (
<View style={S.row}>
{actions.map((a, i) => (
<Pressable key={i} style={S.chip} onPress={() => onSelect(a)}>
<Text style={S.chipText} numberOfLines={2}>
{a}
</Text>
</Pressable>
))}
</View>
)}
</View>
)
}
export default NextActions
const S = StyleSheet.create({
wrap: { backgroundColor: COLORS.card, borderRadius: 14, padding: 14, borderWidth: 1, borderColor: COLORS.border },
title: { fontSize: 13, fontWeight: '700', color: COLORS.text, marginBottom: 8 },
row: { flexDirection: 'row', gap: 8 },
chip: {
flex: 1, backgroundColor: COLORS.light, borderRadius: 10, paddingVertical: 12,
paddingHorizontal: 8, alignItems: 'center', justifyContent: 'center',
},
chipText: { fontSize: 12, fontWeight: '600', color: COLORS.blue, textAlign: 'center' },
})