guardia-messenger/app/(tabs)/offline_ai.tsx

110 lines
5.3 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { View, Text, ScrollView, TouchableOpacity, StyleSheet, Switch } from 'react-native';
import NetInfo from '@react-native-community/netinfo';
interface CacheItem { key: string; size: string; updated: string; category: string }
const CACHE_ITEMS: CacheItem[] = [
{ key: 'kb-docs', size: '12.4MB', updated: '10분 전', category: 'KB 문서' },
{ key: 'runbooks', size: '3.2MB', updated: '1시간 전', category: '런북' },
{ key: 'sr-templates', size: '0.8MB', updated: '30분 전', category: 'SR 템플릿' },
{ key: 'ollama-model', size: '4.7GB', updated: '어제', category: 'AI 모델' },
];
export default function OfflineAIScreen() {
const [isOnline, setIsOnline] = useState(true);
const [offlineEnabled, setOfflineEnabled] = useState(false);
const [syncProgress, setSyncProgress] = useState(0);
const [syncing, setSyncing] = useState(false);
useEffect(() => {
const unsubscribe = NetInfo.addEventListener(state => { setIsOnline(!!state.isConnected); });
return unsubscribe;
}, []);
const startSync = async () => {
setSyncing(true); setSyncProgress(0);
for (let i = 1; i <= 10; i++) {
await new Promise(r => setTimeout(r, 300));
setSyncProgress(i * 10);
}
setSyncing(false);
};
return (
<ScrollView style={s.container}>
<Text style={s.title}> AI</Text>
<Text style={s.sub}> & AI </Text>
<View style={[s.statusCard, { borderColor: isOnline ? '#44bb44' : '#ff4444' }]}>
<View style={[s.statusDot, { backgroundColor: isOnline ? '#44bb44' : '#ff4444' }]} />
<Text style={s.statusText}>{isOnline ? '온라인 — ITSM 서버 연결됨' : '오프라인 — 캐시 모드 동작 중'}</Text>
</View>
<View style={s.card}>
<View style={s.row}>
<Text style={s.label}> </Text>
<Switch value={offlineEnabled} onValueChange={setOfflineEnabled} trackColor={{ true: '#00A0C8', false: '#333' }} />
</View>
<Text style={s.desc}> AI (Ollama ) </Text>
</View>
<View style={s.card}>
<Text style={s.sectionTitle}> </Text>
<View style={s.cacheRow}>
<Text style={s.cacheLabel}> </Text><Text style={s.cacheVal}>4.72GB</Text>
</View>
<View style={s.cacheRow}>
<Text style={s.cacheLabel}> </Text><Text style={s.cacheVal}>10 </Text>
</View>
{syncing && (
<View style={s.progressBar}>
<View style={[s.progressFill, { width: `${syncProgress}%` }]} />
</View>
)}
<TouchableOpacity style={s.syncBtn} onPress={startSync} disabled={syncing || !isOnline}>
<Text style={s.syncBtnText}>{syncing ? `동기화 중 ${syncProgress}%` : '🔄 지금 동기화'}</Text>
</TouchableOpacity>
</View>
<Text style={s.sectionTitle}> </Text>
{CACHE_ITEMS.map((item, i) => (
<View key={i} style={s.cacheItem}>
<View style={s.cacheItemLeft}>
<Text style={s.cacheItemName}>{item.category}</Text>
<Text style={s.cacheItemMeta}>{item.size} · {item.updated} </Text>
</View>
<View style={s.cacheItemRight}>
<Text style={s.cacheItemStatus}></Text>
</View>
</View>
))}
</ScrollView>
);
}
const s = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0A0E1A', padding: 16 },
title: { color: '#fff', fontSize: 20, fontWeight: '700', marginBottom: 4 },
sub: { color: '#888', fontSize: 13, marginBottom: 16 },
statusCard: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#1A1F2E', borderRadius: 12, padding: 14, marginBottom: 12, borderWidth: 1 },
statusDot: { width: 10, height: 10, borderRadius: 5, marginRight: 10 },
statusText: { color: '#fff', fontSize: 14, fontWeight: '600' },
card: { backgroundColor: '#1A1F2E', borderRadius: 12, padding: 16, marginBottom: 16, borderWidth: 1, borderColor: '#333' },
row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
label: { color: '#fff', fontSize: 15, fontWeight: '600' },
desc: { color: '#888', fontSize: 12, marginTop: 8 },
sectionTitle: { color: '#fff', fontSize: 15, fontWeight: '700', marginBottom: 10 },
cacheRow: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 8, borderBottomWidth: 1, borderBottomColor: '#222' },
cacheLabel: { color: '#888' }, cacheVal: { color: '#fff', fontWeight: '600' },
progressBar: { height: 6, backgroundColor: '#333', borderRadius: 3, marginVertical: 12 },
progressFill: { height: 6, backgroundColor: '#00A0C8', borderRadius: 3 },
syncBtn: { backgroundColor: '#00A0C8', padding: 12, borderRadius: 10, alignItems: 'center', marginTop: 12 },
syncBtnText: { color: '#fff', fontWeight: '700' },
cacheItem: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', backgroundColor: '#1A1F2E', borderRadius: 10, padding: 14, marginBottom: 8, borderWidth: 1, borderColor: '#333' },
cacheItemLeft: {}, cacheItemRight: {},
cacheItemName: { color: '#fff', fontWeight: '600', marginBottom: 2 },
cacheItemMeta: { color: '#888', fontSize: 12 },
cacheItemStatus: { fontSize: 18 },
});