/** * meeting_sr.tsx (#18) — 회의 액션아이템 → SR 1-tap 등록 * * meeting.tsx가 SecureStore에 저장한 회의록에서 Ollama로 액션아이템 배열 추출 → * 각 항목을 1-tap으로 SR 등록(createSR). */ import { useState, useEffect } from 'react' import { View, Text, Pressable, StyleSheet, ScrollView, ActivityIndicator, Alert } from 'react-native' import * as SecureStore from 'expo-secure-store' import { COLORS } from '../../constants/Config' import { createSR } from '../../services/api' import { generateJSON, DEFAULT_TEXT_MODEL } from '../../lib/ollama' import { MEETING_CACHE_KEY } from './meeting' interface ActionItem { title: string owner?: string priority?: string } export default function MeetingSRScreen() { const [items, setItems] = useState([]) const [loading, setLoading] = useState(true) const [registered, setRegistered] = useState>({}) useEffect(() => { ;(async () => { setLoading(true) let minutes = '' try { const cached = await SecureStore.getItemAsync(MEETING_CACHE_KEY) if (cached) minutes = JSON.parse(cached).minutes ?? JSON.parse(cached).transcript ?? '' } catch { /* 무시 */ } if (!minutes.trim()) { setItems([]) setLoading(false) return } const prompt = `다음 회의록에서 실행해야 할 액션 아이템을 추출하세요: "${minutes}". ` + `JSON 배열로만 출력: [{"title":"할 일","owner":"담당","priority":"HIGH|MEDIUM|LOW"}]` const result = await generateJSON(DEFAULT_TEXT_MODEL, prompt, []) setItems(Array.isArray(result) ? result.filter(x => x?.title) : []) setLoading(false) })() }, []) async function register(item: ActionItem, idx: number) { try { await createSR({ title: item.title, description: `회의 액션아이템${item.owner ? ` (담당: ${item.owner})` : ''}`, priority: (item.priority ?? 'MEDIUM').toUpperCase(), sr_type: 'OTHER', }) setRegistered(r => ({ ...r, [idx]: true })) Alert.alert('등록 완료', `SR이 접수되었습니다.\n${item.title}`) } catch { Alert.alert('등록 실패', '서버에 연결할 수 없습니다.') } } return ( ✅ 액션아이템 → SR 회의록에서 추출한 할 일을 1-tap으로 SR 등록합니다. {loading ? ( AI가 액션아이템을 추출 중입니다... ) : items.length === 0 ? ( 추출된 액션아이템이 없습니다.{'\n'}먼저 회의 녹음 탭에서 회의록을 생성하세요. ) : ( items.map((item, i) => ( {item.title} {item.owner ? `담당: ${item.owner} · ` : ''}우선순위: {(item.priority ?? 'MEDIUM').toUpperCase()} register(item, i)} disabled={registered[i]} > {registered[i] ? '✓ 등록됨' : 'SR 등록'} )) )} ) } const S = StyleSheet.create({ root: { flex: 1, backgroundColor: COLORS.bg }, header: { padding: 20, paddingBottom: 12, backgroundColor: COLORS.gnbBg }, title: { fontSize: 20, fontWeight: '800', color: '#fff' }, sub: { fontSize: 12, color: 'rgba(255,255,255,0.7)', marginTop: 4 }, center: { alignItems: 'center', padding: 40, gap: 10 }, hint: { fontSize: 13, color: COLORS.muted, textAlign: 'center', lineHeight: 19 }, card: { backgroundColor: COLORS.card, margin: 12, marginBottom: 0, borderRadius: 14, padding: 14, borderWidth: 1, borderColor: COLORS.border }, itemTitle: { fontSize: 14, fontWeight: '700', color: COLORS.text }, itemMeta: { fontSize: 12, color: COLORS.muted, marginTop: 4 }, btn: { marginTop: 10, backgroundColor: COLORS.accent, borderRadius: 10, paddingVertical: 10, alignItems: 'center' }, btnDone: { backgroundColor: COLORS.success }, btnText: { color: '#fff', fontWeight: '700', fontSize: 13 }, })