const $ = (id) => document.getElementById(id);
const state = { sessionId: null, isComplete: false };
const stageLabels = {
intro: 'تمهيد', exploration: 'استكشاف', usecase: 'حالات الاستخدام',
evaluation: 'تقييم', done: 'مكتمل'
};
$('start-btn').addEventListener('click', startSession);
$('project-title').addEventListener('keydown', e => { if (e.key === 'Enter') startSession(); });
$('send-btn').addEventListener('click', sendMessage);
$('input').addEventListener('keydown', e => {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); }
});
$('input').addEventListener('input', e => {
e.target.style.height = 'auto';
e.target.style.height = Math.min(e.target.scrollHeight, 140) + 'px';
});
$('export-btn').addEventListener('click', () => {
if (state.sessionId) window.open(`/report/${state.sessionId}`, '_blank');
});
async function startSession() {
const title = $('project-title').value.trim();
if (!title) { $('project-title').focus(); return; }
$('start-btn').disabled = true;
try {
const res = await fetch('/api/start', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title })
});
const data = await res.json();
if (!res.ok) throw new Error(data.error || 'فشل');
state.sessionId = data.sessionId;
$('welcome').classList.add('hidden');
$('app').classList.remove('hidden');
$('project-name').textContent = title;
addAgentMessage(data.reply, true);
updateState(data);
} catch (e) {
alert('خطأ: ' + e.message);
$('start-btn').disabled = false;
}
}
async function sendMessage() {
const input = $('input');
const text = input.value.trim();
if (!text || !state.sessionId) return;
addUserMessage(text);
input.value = '';
input.style.height = 'auto';
$('send-btn').disabled = true;
const thinkingEl = addAgentMessage('يفكّر...', false, true);
try {
const res = await fetch('/api/message', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sessionId: state.sessionId, message: text })
});
const data = await res.json();
if (!res.ok) throw new Error(data.error || 'فشل');
thinkingEl.remove();
addAgentMessage(data.reply, true);
updateState(data);
} catch (e) {
thinkingEl.querySelector('.bubble').textContent = '⚠️ ' + e.message;
thinkingEl.classList.remove('thinking');
} finally {
$('send-btn').disabled = false;
$('input').focus();
}
}
function addUserMessage(text) {
const el = document.createElement('div');
el.className = 'msg user';
el.innerHTML = `
أنا
`;
el.querySelector('.bubble').textContent = text;
$('messages').appendChild(el);
scrollMsgs();
}
function addAgentMessage(text, typewriter = false, thinking = false) {
document.querySelectorAll('.msg.agent.fresh').forEach(e => e.classList.remove('fresh'));
const el = document.createElement('div');
el.className = 'msg agent fresh' + (thinking ? ' thinking' : '');
el.innerHTML = `AI
`;
$('messages').appendChild(el);
const bubble = el.querySelector('.bubble');
if (typewriter) typeText(bubble, text);
else bubble.textContent = text;
scrollMsgs();
return el;
}
function typeText(el, text) {
el.textContent = '';
const cursor = document.createElement('span');
cursor.className = 'cursor';
el.appendChild(cursor);
let i = 0;
const speed = Math.max(8, Math.min(28, 1200 / text.length));
const tick = () => {
if (i < text.length) {
cursor.insertAdjacentText('beforebegin', text[i]);
i++;
scrollMsgs();
setTimeout(tick, speed);
} else {
cursor.remove();
}
};
tick();
}
function scrollMsgs() {
const m = $('messages');
m.scrollTop = m.scrollHeight;
}
function updateState(data) {
const fps = data.functionPoints || [];
const ucs = data.useCases || [];
const actors = data.actors || [];
$('fp-count').textContent = fps.length;
$('uc-count').textContent = ucs.length;
$('fp-total').textContent = fps.reduce((s, f) => s + (Number(f.fpScore) || 0), 0);
$('stage-pill').textContent = stageLabels[data.stage] || data.stage || '—';
// Tree
const tree = $('tree');
if (!actors.length && !fps.length) {
tree.innerHTML = 'ستظهر الـ Actors والوظائف هنا تلقائياً.
';
} else {
let html = '';
if (actors.length) {
actors.forEach(a => {
html += `${esc(a)}
`;
});
}
fps.forEach(f => {
html += `${esc(f.id || '')} · ${esc(f.name || '')}
`;
});
tree.innerHTML = html;
}
// Live preview
const prev = $('preview-content');
let html = '';
if (data.scope) html += `نطاق النظام
${esc(data.scope)}
`;
if (actors.length) {
html += `المستخدمون
${actors.map(esc).join(' · ')}
`;
}
if (fps.length) {
html += `المتطلبات الوظيفية (${fps.length})
`;
fps.forEach(f => {
html += `${esc(f.id || '')} ${esc(f.name || '')} — ${esc(f.complexity || '')} (FP ${esc(String(f.fpScore || ''))})
`;
});
}
if (ucs.length) {
html += `حالات الاستخدام (${ucs.length})
`;
ucs.forEach(u => {
html += `${esc(u.id || '')} ${esc(u.title || '')}
الفاعل: ${esc(u.actor || '')}
`;
});
}
prev.innerHTML = html || 'سيتشكّل النص الهندسي للتقرير هنا أثناء الحوار.
';
// Show export when there's enough content or marked complete
if (data.isComplete || (fps.length >= 3 && ucs.length >= 1)) {
$('export-btn').classList.remove('hidden');
}
state.isComplete = !!data.isComplete;
}
function esc(s) {
return String(s ?? '').replace(/[&<>"']/g, c => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c]));
}