// AI Asistan — uçan buton + sohbet paneli. // Dış bir AI servisine bağlanmaz: kural tabanlı, tamamen yerel bir niyet // tanıma motoru kullanır. Ağ isteği yok, API anahtarı yok, rate limit yok — // yanıtlar doğrudan uygulamanın kendi verisinden (state) üretilir. const AWIcons = window.Icons; const { isOverdue: awOverdue, uid: awUid } = window.PMStore; const AW_HISTORY_KEY = 'navia_ai_chat_v1'; function awLoadHistory() { try { return JSON.parse(localStorage.getItem(AW_HISTORY_KEY) || '[]'); } catch (e) { return []; } } function awSaveHistory(msgs) { try { localStorage.setItem(AW_HISTORY_KEY, JSON.stringify(msgs.slice(-30))); } catch (e) {} } function awNorm(s) { return (s || '').toLocaleLowerCase('tr-TR').trim(); } function awDateKey(d) { return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; } // JS'in \b sınırı yalnızca ASCII [A-Za-z0-9_] tanır — ç/ğ/ı/ö/ş/ü ile başlayan/biten // kelimelerde (örn. "kaç", "önemli") sessizce başarısız olur. Türkçe-güvenli alternatif. const AW_LETTERS = 'a-zçğıiöşü'; function awWord(alt) { return new RegExp(`(^|[^${AW_LETTERS}])(${alt})($|[^${AW_LETTERS}])`); } function awParseDue(text) { // Türkçe hal ekleri ("yarına", "bugüne") gövdeye eklendiği için sondaki \b aranmaz — // yalnızca kelime başlangıcı kontrol edilir. const now = new Date(); now.setHours(0, 0, 0, 0); if (awWord('öbür\\s*gün').test(text)) { const d = new Date(now); d.setDate(d.getDate() + 2); return awDateKey(d); } if (/\bbugün/.test(text)) return awDateKey(now); if (/\byarın/.test(text)) { const d = new Date(now); d.setDate(d.getDate() + 1); return awDateKey(d); } const m = text.match(/(\d+)\s*gün\s*sonra/); if (m) { const d = new Date(now); d.setDate(d.getDate() + parseInt(m[1], 10)); return awDateKey(d); } if (/\bgelecek\s*hafta\b|\bhaftaya\b/.test(text)) { const d = new Date(now); d.setDate(d.getDate() + 7); return awDateKey(d); } return null; } // Tırnak içi > "diye" kalıbı > "X görev ekle" kalıbı sırasıyla başlık çıkarmayı dener. function awExtractTitle(raw) { const q = raw.match(/["'“”‘’]([^"'“”‘’]{2,90})["'“”‘’]/); if (q) return q[1].trim(); const d = raw.match(/^(.*?)\s+diye\b/i); if (d) { let t = d[1].trim().replace(/^(yarına|bugüne|haftaya|gelecek hafta|öbür güne)\s+/i, '').trim(); if (t) return t; } const g = raw.match(/^(.*?)\s+(için\s+)?(görev|iş)\s*(olarak)?\s*(ekle|oluştur)/i); if (g && g[1].trim()) return g[1].trim(); return null; } // Metin içinde geçen en uzun (en spesifik) görev başlığını bulur. function awFindMentionedTask(tasks, textLower) { let best = null, bestLen = 0; for (const t of tasks) { const tl = awNorm(t.title); if (tl.length >= 3 && textLower.includes(tl) && tl.length > bestLen) { best = t; bestLen = tl.length; } } return best; } // **kalın** işaretli metni satır satır React node'larına çevirir — chat balonlarında // düz yıldız karakteri yerine gerçek kalın yazı görünsün diye. function awRenderContent(text) { const lines = (text || '').split('\n'); return lines.map((line, li) => { const parts = line.split(/(\*\*[^*]+\*\*)/g).filter(Boolean); return ( {li > 0 &&
} {parts.map((part, pi) => ( part.startsWith('**') && part.endsWith('**') ? {part.slice(2, -2)} : {part} ))}
); }); } const AW_HELP = [ 'Şunları deneyebilirsin:', '· Bugün ne yapmam gerekiyor?', '· Gecikmiş görevlerim neler?', '· Kaç açık görevim var?', '· **"Toplantı notu hazırla"** diye yarına görev ekle', '· Toplantı notu hazırla görevini tamamla', '· Toplantı notu hazırla görevini sil', ].join('\n'); // Kural tabanlı yerel yanıt motoru — { reply, action } döner. Ağ isteği yapmaz. function awLocalReply(rawText, state) { const text = awNorm(rawText); const today = new Date(); today.setHours(0, 0, 0, 0); const todayKey = awDateKey(today); const tasks = (state.tasks || []).filter(t => !t.archived); const openTasks = tasks.filter(t => t.status !== 'done'); if (/^(merhaba|selam|hey|naber|nasılsın|iyi günler|günaydın)\b/.test(text)) { return { reply: 'Merhaba! Görevlerin hakkında sorular sorabilir ya da benden görev ekleyip tamamlamamı isteyebilirsin.\n\n' + AW_HELP }; } if (/\b(yardım|ne yapabilirsin|neler yapabilirsin)\b/.test(text)) { return { reply: AW_HELP }; } // Görev oluşturma — "sil/tamamla" ile çakışmasın diye önce elenir if (/\b(ekle|oluştur)\b/.test(text) && /\b(görev|iş)/.test(text) && !/\bsil\b/.test(text)) { const title = awExtractTitle(rawText); if (!title) { return { reply: 'Görev başlığını tam olarak anlayamadım. Şu kalıbı dener misin:\n"Görev adı" diye yarına görev ekle' }; } const due = awParseDue(text); const priority = (/\byüksek\b|\bacil\b/.test(text) || awWord('önemli').test(text)) ? 'high' : /\bdüşük\b/.test(text) ? 'low' : 'medium'; return { reply: `Tamam, ${due ? '**' + due + '** tarihine ' : ''}**${title}** görevini ekliyorum.`, action: { type: 'createTask', title, due, priority }, }; } // Sprint oluşturma if (/\bsprint\b/.test(text) && /\b(ekle|oluştur)\b/.test(text)) { const name = awExtractTitle(rawText) || 'Yeni Sprint'; return { reply: `Tamam, **${name}** adında bir sprint oluşturuyorum.`, action: { type: 'createSprint', name } }; } // Görev silme if (/\bsil\b/.test(text)) { const task = awFindMentionedTask(openTasks.length ? openTasks : tasks, text); if (!task) return { reply: 'Hangi görevi silmemi istersin? Tam başlığını yazar mısın?' }; return { reply: `**${task.title}** görevini siliyorum.`, action: { type: 'deleteTask', taskTitle: task.title } }; } // Görev tamamlama if (/\btamamla\b|\bbitir\b|\bbitti\b/.test(text)) { const task = awFindMentionedTask(openTasks, text); if (!task) return { reply: 'Hangi görevi tamamlandı olarak işaretleyeyim? Tam başlığını yazar mısın?' }; return { reply: `**${task.title}** görevini tamamlandı olarak işaretliyorum.`, action: { type: 'updateTask', taskTitle: task.title, patch: { status: 'done' } } }; } // Görev başlatma if (/\bbaşlat\b/.test(text)) { const task = awFindMentionedTask(tasks.filter(t => t.status === 'todo'), text); if (!task) return { reply: 'Hangi görevi devam ediyor durumuna almamı istersin?' }; return { reply: `**${task.title}** görevini devam ediyor durumuna alıyorum.`, action: { type: 'updateTask', taskTitle: task.title, patch: { status: 'doing' } } }; } // Gecikmiş görevler if (/gecik/.test(text)) { const overdue = openTasks.filter(t => awOverdue?.(t.due, t.status)); if (!overdue.length) return { reply: 'Gecikmiş görevin yok — her şey yolunda 🎉' }; return { reply: `**${overdue.length}** gecikmiş görevin var:\n` + overdue.slice(0, 10).map(t => `· **${t.title}** (${t.due})`).join('\n') }; } // Bugün teslim görevler if (/\bbugün/.test(text) && /(görev|iş|yapmam|teslim)/.test(text)) { const dueToday = openTasks.filter(t => t.due === todayKey); if (!dueToday.length) return { reply: 'Bugün teslim edilmesi gereken bir görevin yok.' }; return { reply: `Bugün teslim olan **${dueToday.length}** görev var:\n` + dueToday.map(t => `· **${t.title}**`).join('\n') }; } // Sayım sorguları if ((awWord('kaç').test(text) || /\btoplam\b/.test(text)) && /(görev|iş)/.test(text)) { return { reply: `**${openTasks.length}** açık, **${tasks.length - openTasks.length}** tamamlanmış görevin var. Toplam **${tasks.length}** görev.` }; } // Tamamlanan görevler if (/tamamlanan|biten görev/.test(text)) { const doneThisMonth = tasks.filter(t => t.status === 'done' && (t.completedAt || '').slice(0, 7) === todayKey.slice(0, 7)); return { reply: `Bu ay **${doneThisMonth.length}** görev tamamladın. Toplamda **${tasks.filter(t => t.status === 'done').length}** görev tamamlanmış.` }; } // Proje listesi if (/proje/.test(text) && /(liste|neler|hangi|kaç)/.test(text)) { const projects = state.projects || []; if (!projects.length) return { reply: 'Henüz hiç projen yok.' }; return { reply: `**${projects.length}** projen var:\n` + projects.map(p => `· ${p.name}`).join('\n') }; } return { reply: 'Bunu tam anlayamadım. ' + AW_HELP }; } function AIWidget({ state, session, onCreateTask, onUpdateTask, onDeleteTask, onCreateSprint }) { const [open, setOpen] = React.useState(false); const [messages, setMessages] = React.useState(awLoadHistory); const [input, setInput] = React.useState(''); const [loading, setLoading] = React.useState(false); const listRef = React.useRef(null); const isPro = state.plan === 'pro' || state.isAdmin; React.useEffect(() => { awSaveHistory(messages); }, [messages]); React.useEffect(() => { if (listRef.current) listRef.current.scrollTop = listRef.current.scrollHeight; }, [messages, open, loading]); const applyAction = (action) => { if (!action || !action.type) return null; try { if (action.type === 'createTask') { onCreateTask({ id: awUid(), title: action.title || 'Yeni görev', due: action.due || null, priority: ['low', 'medium', 'high'].includes(action.priority) ? action.priority : 'medium', projectId: state.activeProjectId || (state.projects[0] && state.projects[0].id) || null, }); return `✓ Görev oluşturuldu: **${action.title || 'Yeni görev'}**`; } if (action.type === 'updateTask') { const task = (state.tasks || []).find(t => t.title === action.taskTitle); if (!task) return null; onUpdateTask(task.id, action.patch || {}); return `✓ **${task.title}** güncellendi.`; } if (action.type === 'deleteTask') { const task = (state.tasks || []).find(t => t.title === action.taskTitle); if (!task) return null; onDeleteTask(task.id); return `✓ **${task.title}** silindi.`; } if (action.type === 'createSprint') { onCreateSprint({ projectId: state.activeProjectId || (state.projects[0] && state.projects[0].id) || null, name: action.name || 'Yeni Sprint', goal: '', status: 'planning', }); return `✓ Sprint oluşturuldu: **${action.name || 'Yeni Sprint'}**`; } } catch (e) { return '⚠ Eylem uygulanırken bir sorun oluştu.'; } return null; }; const send = () => { const text = input.trim(); if (!text || loading) return; setInput(''); const nextMsgs = [...messages, { role: 'user', content: text }]; setMessages(nextMsgs); setLoading(true); // Küçük bir gecikme — anlık yanıt "düşünmeden atıyor" hissi vermesin setTimeout(() => { const { reply, action } = awLocalReply(text, state); const actionNote = applyAction(action); const finalMsgs = [...nextMsgs, { role: 'assistant', content: reply }]; if (actionNote) finalMsgs.push({ role: 'system', content: actionNote }); setMessages(finalMsgs); setLoading(false); }, 280); }; const clearChat = () => { setMessages([]); awSaveHistory([]); }; return ( <> {open && (
AI Asistan
{isPro ? 'Görev ve proje konusunda sor' : 'Pro plana özel'}
{isPro && messages.length > 0 && ( )}
{!isPro ? (
🚀

AI Asistan, görevlerini konuşarak yönetmene yardımcı olur — Pro plana özel bir özellik.

Pro'ya Geç — Bilgi Al
) : ( <>
{messages.length === 0 && (
Merhaba{session?.name ? ', ' + session.name.split(' ')[0] : ''} 👋
"Bugün ne yapmam gerekiyor?", "Gecikmiş işlerim neler?" gibi sorular sorabilir, ya da "'X' diye yarına görev ekle" diyebilirsin.
)} {messages.map((m, i) => (
{awRenderContent(m.content)}
))} {loading &&
}