codex-ai-platform / src /components /FileWatcher.tsx
3v324v23's picture
chore: 彻底清理项目,符合 Hugging Face 部署规范
ae4ceef
import React, { useEffect, useState } from 'react';
import { useStore } from '@/store/useStore';
import { useTranslation } from 'react-i18next';
import { Bell, Folder, Upload, X } from 'lucide-react';
import { invoke } from '@tauri-apps/api/core';
import { listen } from '@tauri-apps/api/event';
export const FileWatcher: React.FC = () => {
const { t } = useTranslation();
const [isTauri, setIsTauri] = useState(false);
const [watchedPath, setWatchedPath] = useState<string | null>(null);
const [isWatching, setIsWatching] = useState(false);
const [notifications, setNotifications] = useState<any[]>([]);
const { addChatMessage } = useStore();
useEffect(() => {
// Check if running in Tauri
if ((window as any).__TAURI_INTERNALS__) {
setIsTauri(true);
}
}, []);
useEffect(() => {
if (!isTauri) return;
// Listen for file-change events from Tauri
const unlisten = listen('file-changed', async (event: any) => {
const { path, filename, action } = event.payload;
if (action === 'create' || action === 'modify') {
const newNotif = {
id: Date.now(),
filename,
path,
status: 'detected',
timestamp: new Date().toLocaleTimeString()
};
setNotifications(prev => [newNotif, ...prev].slice(0, 5));
// Auto-upload and parse logic (mocked for now)
// In a real app, we would read the file content via Tauri fs and upload to server
addChatMessage({
id: `file-${Date.now()}`,
role: 'system',
content: `Detected file: ${filename}. Automatically parsing...`,
timestamp: Date.now()
});
}
});
return () => {
unlisten.then(f => f());
};
}, [isTauri, addChatMessage]);
const startWatching = async () => {
try {
// In a real Tauri app, we'd use a dialog to pick a folder
// For this demo, we'll use a mocked path or the downloads folder
const path = await invoke('start_watching', { path: '/Users/by/Downloads/codex_watch' });
setWatchedPath(path as string);
setIsWatching(true);
} catch (err) {
console.error('Failed to start watching:', err);
}
};
const stopWatching = async () => {
try {
await invoke('stop_watching');
setIsWatching(false);
} catch (err) {
console.error('Failed to stop watching:', err);
}
};
if (!isTauri) return null;
return (
<div className="fixed bottom-4 right-4 z-50 flex flex-col items-end gap-2">
{/* Notifications */}
{notifications.map(n => (
<div key={n.id} className="bg-white dark:bg-slate-800 p-3 rounded-lg shadow-lg border border-indigo-500/30 flex items-center gap-3 animate-in fade-in slide-in-from-right-4">
<div className="p-2 bg-indigo-100 dark:bg-indigo-900/50 rounded-full text-indigo-600">
<Upload size={16} />
</div>
<div className="flex flex-col">
<span className="text-xs font-bold truncate max-w-[150px]">{n.filename}</span>
<span className="text-[10px] text-slate-500">{n.timestamp}</span>
</div>
<button onClick={() => setNotifications(prev => prev.filter(x => x.id !== n.id))} className="text-slate-400 hover:text-slate-600">
<X size={14} />
</button>
</div>
))}
{/* Control Panel */}
<div className="bg-white dark:bg-slate-800 p-2 rounded-full shadow-xl border border-slate-200 dark:border-slate-700 flex items-center gap-2">
{isWatching ? (
<div className="flex items-center gap-2 px-3">
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
<span className="text-xs font-medium text-slate-600 dark:text-slate-300">Watching...</span>
<button
onClick={stopWatching}
className="p-1 hover:bg-slate-100 dark:hover:bg-slate-700 rounded-full text-red-500"
>
<X size={14} />
</button>
</div>
) : (
<button
onClick={startWatching}
className="flex items-center gap-2 px-4 py-1.5 bg-indigo-600 hover:bg-indigo-700 text-white rounded-full text-xs font-bold transition-all"
>
<Folder size={14} />
Start File Watcher
</button>
)}
</div>
</div>
);
};