110 lines
5.3 KiB
TypeScript
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 },
|
|
});
|