Compare commits
No commits in common. "ai-dev" and "main" have entirely different histories.
702
db/sessions.json
702
db/sessions.json
@ -1785,707 +1785,5 @@
|
|||||||
"createdAt": "T08:15:12.895Z",
|
"createdAt": "T08:15:12.895Z",
|
||||||
"updatedAt": "T08:15:12.915Z",
|
"updatedAt": "T08:15:12.915Z",
|
||||||
"stage": "intro"
|
"stage": "intro"
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "sess_vsyycl02mp83hq7p",
|
|
||||||
"title": "سثث",
|
|
||||||
"scope": "",
|
|
||||||
"actors": [],
|
|
||||||
"useCases": [],
|
|
||||||
"inputOutputs": [],
|
|
||||||
"functionPoints": [],
|
|
||||||
"planning": {
|
|
||||||
"developers": 3,
|
|
||||||
"sprintWeeks": 2,
|
|
||||||
"hoursPerDay": 8
|
|
||||||
},
|
|
||||||
"history": [
|
|
||||||
{
|
|
||||||
"role": "assistant",
|
|
||||||
"payload": {
|
|
||||||
"reply": "بدأنا مشروعاً جديداً بعنوان \"سثث\". هل تود أن تبدأ برفع ملف SRS، أم تصف لي الهدف العام للمشروع مباشرة؟",
|
|
||||||
"stage": "intro",
|
|
||||||
"scope": "",
|
|
||||||
"actors": [],
|
|
||||||
"functionPoints": [],
|
|
||||||
"useCases": [],
|
|
||||||
"inputOutputs": [],
|
|
||||||
"isComplete": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"srsDraft": null,
|
|
||||||
"needsStructureConfirmation": false,
|
|
||||||
"isComplete": false,
|
|
||||||
"createdAt": "T08:39:23.893Z",
|
|
||||||
"updatedAt": "T08:39:23.894Z",
|
|
||||||
"stage": "intro"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "sess_u7o8c5d0mp83wmtt",
|
|
||||||
"title": "فحص واجهة الشات",
|
|
||||||
"scope": "نظام لفحص واجهة الشات يسمح للمستخدمين بتسجيل الدخول، للمديرين بمراجعة الطلبات، وللعملاء بإرسال طلبات الخدمة، مع إصدار النظام لتقارير شهرية.",
|
|
||||||
"actors": [
|
|
||||||
"المستخدم",
|
|
||||||
"المدير",
|
|
||||||
"العميل",
|
|
||||||
"النظام"
|
|
||||||
],
|
|
||||||
"useCases": [
|
|
||||||
{
|
|
||||||
"id": "UC-01",
|
|
||||||
"title": "تسجيل الدخول",
|
|
||||||
"actor": "المستخدم",
|
|
||||||
"preconditions": "يجب أن يكون لدى المستخدم حساب صالح.",
|
|
||||||
"mainFlow": [
|
|
||||||
"يقوم المستخدم بإدخال اسم المستخدم وكلمة المرور.",
|
|
||||||
"يقوم النظام بالتحقق من صحة بيانات الاعتماد.",
|
|
||||||
"إذا كانت البيانات صحيحة، يتم تسجيل دخول المستخدم بنجاح.",
|
|
||||||
"إذا كانت البيانات غير صحيحة، يتم عرض رسالة خطأ."
|
|
||||||
],
|
|
||||||
"alternateFlow": [
|
|
||||||
"في حالة نسيان كلمة المرور، يمكن للمستخدم طلب إعادة تعيينها."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "UC-02",
|
|
||||||
"title": "مراجعة الطلبات",
|
|
||||||
"actor": "المدير",
|
|
||||||
"preconditions": "يجب أن يكون لدى المدير صلاحيات الوصول إلى قسم مراجعة الطلبات.",
|
|
||||||
"mainFlow": [
|
|
||||||
"يقوم المدير بالوصول إلى قسم مراجعة الطلبات.",
|
|
||||||
"يعرض النظام قائمة بالطلبات المقدمة.",
|
|
||||||
"يمكن للمدير اختيار طلب معين لعرض تفاصيله.",
|
|
||||||
"يمكن للمدير تحديث حالة الطلب (مثل: قيد المعالجة، مكتمل، مرفوض)."
|
|
||||||
],
|
|
||||||
"alternateFlow": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "UC-03",
|
|
||||||
"title": "إصدار تقرير شهري",
|
|
||||||
"actor": "النظام",
|
|
||||||
"preconditions": "يجب أن يكون النظام قادراً على الوصول إلى بيانات الطلبات.",
|
|
||||||
"mainFlow": [
|
|
||||||
"في نهاية كل شهر، يقوم النظام بتشغيل عملية إنشاء التقرير.",
|
|
||||||
"يقوم النظام بتجميع بيانات الطلبات خلال الشهر.",
|
|
||||||
"يقوم النظام بإنشاء تقرير شهري يتضمن ملخصاً للطلبات وحالاتها."
|
|
||||||
],
|
|
||||||
"alternateFlow": [
|
|
||||||
"يمكن للمدير طلب إنشاء تقرير في أي وقت."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "UC-04",
|
|
||||||
"title": "إرسال طلب خدمة",
|
|
||||||
"actor": "العميل",
|
|
||||||
"preconditions": "يجب أن يكون لدى العميل حساب أو القدرة على تقديم طلب كضيف.",
|
|
||||||
"mainFlow": [
|
|
||||||
"يقوم العميل بالوصول إلى واجهة تقديم طلب الخدمة.",
|
|
||||||
"يقوم العميل بإدخال تفاصيل الطلب (مثل: وصف الخدمة المطلوبة، معلومات الاتصال).",
|
|
||||||
"يقوم العميل بإرسال الطلب.",
|
|
||||||
"يقوم النظام بتسجيل الطلب وتعيين معرف فريد له.",
|
|
||||||
"يعرض النظام رسالة تأكيد للعميل."
|
|
||||||
],
|
|
||||||
"alternateFlow": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"inputOutputs": [
|
|
||||||
{
|
|
||||||
"id": "IO-01",
|
|
||||||
"type": "input",
|
|
||||||
"name": "بيانات تسجيل الدخول (اسم المستخدم، كلمة المرور)",
|
|
||||||
"source": "المستخدم",
|
|
||||||
"destination": "النظام",
|
|
||||||
"description": "المعلومات التي يدخلها المستخدم لتسجيل الدخول."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "IO-02",
|
|
||||||
"type": "output",
|
|
||||||
"name": "تأكيد تسجيل الدخول / رسالة خطأ",
|
|
||||||
"source": "النظام",
|
|
||||||
"destination": "المستخدم",
|
|
||||||
"description": "نتيجة عملية تسجيل الدخول."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "IO-03",
|
|
||||||
"type": "input",
|
|
||||||
"name": "معرف الطلب / تحديث الحالة",
|
|
||||||
"source": "المدير",
|
|
||||||
"destination": "النظام",
|
|
||||||
"description": "المعلومات التي يدخلها المدير لتحديث حالة الطلب أو مراجعته."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "IO-04",
|
|
||||||
"type": "output",
|
|
||||||
"name": "قائمة الطلبات / تفاصيل الطلب",
|
|
||||||
"source": "النظام",
|
|
||||||
"destination": "المدير",
|
|
||||||
"description": "البيانات التي يعرضها النظام للمدير لمراجعة الطلبات."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "IO-05",
|
|
||||||
"type": "output",
|
|
||||||
"name": "التقرير الشهري",
|
|
||||||
"source": "النظام",
|
|
||||||
"destination": "المدير",
|
|
||||||
"description": "التقرير الذي يولده النظام شهرياً."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "IO-06",
|
|
||||||
"type": "input",
|
|
||||||
"name": "تفاصيل طلب الخدمة (وصف، معلومات الاتصال)",
|
|
||||||
"source": "العميل",
|
|
||||||
"destination": "النظام",
|
|
||||||
"description": "المعلومات التي يقدمها العميل لإنشاء طلب خدمة."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "IO-07",
|
|
||||||
"type": "output",
|
|
||||||
"name": "تأكيد تقديم الطلب",
|
|
||||||
"source": "النظام",
|
|
||||||
"destination": "العميل",
|
|
||||||
"description": "رسالة تأكيد للعميل بعد تقديم طلب الخدمة."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"functionPoints": [
|
|
||||||
{
|
|
||||||
"id": "FP-01",
|
|
||||||
"name": "تسجيل الدخول",
|
|
||||||
"description": "تغطي حالة الاستخدام \"تسجيل الدخول\" للفاعل المستخدم.",
|
|
||||||
"complexity": "معقدة",
|
|
||||||
"fpScore": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "FP-02",
|
|
||||||
"name": "مراجعة الطلبات",
|
|
||||||
"description": "تغطي حالة الاستخدام \"مراجعة الطلبات\" للفاعل المدير.",
|
|
||||||
"complexity": "متوسطة",
|
|
||||||
"fpScore": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "FP-03",
|
|
||||||
"name": "إصدار تقرير شهري",
|
|
||||||
"description": "تغطي حالة الاستخدام \"إصدار تقرير شهري\" للفاعل النظام.",
|
|
||||||
"complexity": "معقدة",
|
|
||||||
"fpScore": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "FP-04",
|
|
||||||
"name": "إرسال طلب خدمة",
|
|
||||||
"description": "تغطي حالة الاستخدام \"إرسال طلب خدمة\" للفاعل العميل.",
|
|
||||||
"complexity": "متوسطة",
|
|
||||||
"fpScore": 4
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"planning": {
|
|
||||||
"developers": 3,
|
|
||||||
"sprintWeeks": 2,
|
|
||||||
"hoursPerDay": 8
|
|
||||||
},
|
|
||||||
"history": [
|
|
||||||
{
|
|
||||||
"role": "assistant",
|
|
||||||
"payload": {
|
|
||||||
"reply": "بدأنا مشروعاً جديداً بعنوان \"فحص واجهة الشات\". هل تود أن تبدأ برفع ملف SRS، أم تصف لي الهدف العام للمشروع مباشرة؟",
|
|
||||||
"stage": "intro",
|
|
||||||
"scope": "",
|
|
||||||
"actors": [],
|
|
||||||
"functionPoints": [],
|
|
||||||
"useCases": [],
|
|
||||||
"inputOutputs": [],
|
|
||||||
"isComplete": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "assistant",
|
|
||||||
"payload": {
|
|
||||||
"reply": "تم تحليل النص المرفوع. اقترحت 4 Actors و4 Use Cases و3 Inputs و4 Outputs. هل تفاصيل حالات الاستخدام والمدخلات والمخرجات والوظائف المستخرجة تعكس بدقة متطلبات النظام المذكورة في نص SRS؟",
|
|
||||||
"stage": "exploration",
|
|
||||||
"scope": "نظام لفحص واجهة الشات يسمح للمستخدمين بتسجيل الدخول، للمديرين بمراجعة الطلبات، وللعملاء بإرسال طلبات الخدمة، مع إصدار النظام لتقارير شهرية.",
|
|
||||||
"actors": [
|
|
||||||
"المستخدم",
|
|
||||||
"المدير",
|
|
||||||
"العميل",
|
|
||||||
"النظام"
|
|
||||||
],
|
|
||||||
"functionPoints": [
|
|
||||||
{
|
|
||||||
"id": "FP-01",
|
|
||||||
"name": "تسجيل الدخول",
|
|
||||||
"description": "وظيفة تسجيل دخول المستخدمين إلى النظام.",
|
|
||||||
"complexity": "متوسطة",
|
|
||||||
"fpScore": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "FP-02",
|
|
||||||
"name": "إدارة الطلبات",
|
|
||||||
"description": "وظيفة مراجعة وتحديث حالات الطلبات من قبل المدير.",
|
|
||||||
"complexity": "معقدة",
|
|
||||||
"fpScore": 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "FP-03",
|
|
||||||
"name": "إنشاء تقرير شهري",
|
|
||||||
"description": "وظيفة توليد تقرير شهري آلياً بواسطة النظام.",
|
|
||||||
"complexity": "معقدة",
|
|
||||||
"fpScore": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "FP-04",
|
|
||||||
"name": "تقديم طلب خدمة",
|
|
||||||
"description": "وظيفة تمكين العملاء من إرسال طلبات الخدمة.",
|
|
||||||
"complexity": "متوسطة",
|
|
||||||
"fpScore": 5
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"useCases": [
|
|
||||||
{
|
|
||||||
"id": "UC-01",
|
|
||||||
"title": "تسجيل الدخول",
|
|
||||||
"actor": "المستخدم",
|
|
||||||
"preconditions": "يجب أن يكون لدى المستخدم حساب صالح.",
|
|
||||||
"mainFlow": [
|
|
||||||
"يقوم المستخدم بإدخال اسم المستخدم وكلمة المرور.",
|
|
||||||
"يقوم النظام بالتحقق من صحة بيانات الاعتماد.",
|
|
||||||
"إذا كانت البيانات صحيحة، يتم تسجيل دخول المستخدم بنجاح.",
|
|
||||||
"إذا كانت البيانات غير صحيحة، يتم عرض رسالة خطأ."
|
|
||||||
],
|
|
||||||
"alternateFlow": [
|
|
||||||
"في حالة نسيان كلمة المرور، يمكن للمستخدم طلب إعادة تعيينها."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "UC-02",
|
|
||||||
"title": "مراجعة الطلبات",
|
|
||||||
"actor": "المدير",
|
|
||||||
"preconditions": "يجب أن يكون لدى المدير صلاحيات الوصول إلى قسم مراجعة الطلبات.",
|
|
||||||
"mainFlow": [
|
|
||||||
"يقوم المدير بالوصول إلى قسم مراجعة الطلبات.",
|
|
||||||
"يعرض النظام قائمة بالطلبات المقدمة.",
|
|
||||||
"يمكن للمدير اختيار طلب معين لعرض تفاصيله.",
|
|
||||||
"يمكن للمدير تحديث حالة الطلب (مثل: قيد المعالجة، مكتمل، مرفوض)."
|
|
||||||
],
|
|
||||||
"alternateFlow": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "UC-03",
|
|
||||||
"title": "إصدار تقرير شهري",
|
|
||||||
"actor": "النظام",
|
|
||||||
"preconditions": "يجب أن يكون النظام قادراً على الوصول إلى بيانات الطلبات.",
|
|
||||||
"mainFlow": [
|
|
||||||
"في نهاية كل شهر، يقوم النظام بتشغيل عملية إنشاء التقرير.",
|
|
||||||
"يقوم النظام بتجميع بيانات الطلبات خلال الشهر.",
|
|
||||||
"يقوم النظام بإنشاء تقرير شهري يتضمن ملخصاً للطلبات وحالاتها."
|
|
||||||
],
|
|
||||||
"alternateFlow": [
|
|
||||||
"يمكن للمدير طلب إنشاء تقرير في أي وقت."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "UC-04",
|
|
||||||
"title": "إرسال طلب خدمة",
|
|
||||||
"actor": "العميل",
|
|
||||||
"preconditions": "يجب أن يكون لدى العميل حساب أو القدرة على تقديم طلب كضيف.",
|
|
||||||
"mainFlow": [
|
|
||||||
"يقوم العميل بالوصول إلى واجهة تقديم طلب الخدمة.",
|
|
||||||
"يقوم العميل بإدخال تفاصيل الطلب (مثل: وصف الخدمة المطلوبة، معلومات الاتصال).",
|
|
||||||
"يقوم العميل بإرسال الطلب.",
|
|
||||||
"يقوم النظام بتسجيل الطلب وتعيين معرف فريد له.",
|
|
||||||
"يعرض النظام رسالة تأكيد للعميل."
|
|
||||||
],
|
|
||||||
"alternateFlow": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"inputOutputs": [
|
|
||||||
{
|
|
||||||
"id": "IO-01",
|
|
||||||
"type": "input",
|
|
||||||
"name": "بيانات تسجيل الدخول (اسم المستخدم، كلمة المرور)",
|
|
||||||
"source": "المستخدم",
|
|
||||||
"destination": "النظام",
|
|
||||||
"description": "المعلومات التي يدخلها المستخدم لتسجيل الدخول."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "IO-02",
|
|
||||||
"type": "output",
|
|
||||||
"name": "تأكيد تسجيل الدخول / رسالة خطأ",
|
|
||||||
"source": "النظام",
|
|
||||||
"destination": "المستخدم",
|
|
||||||
"description": "نتيجة عملية تسجيل الدخول."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "IO-03",
|
|
||||||
"type": "input",
|
|
||||||
"name": "معرف الطلب / تحديث الحالة",
|
|
||||||
"source": "المدير",
|
|
||||||
"destination": "النظام",
|
|
||||||
"description": "المعلومات التي يدخلها المدير لتحديث حالة الطلب أو مراجعته."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "IO-04",
|
|
||||||
"type": "output",
|
|
||||||
"name": "قائمة الطلبات / تفاصيل الطلب",
|
|
||||||
"source": "النظام",
|
|
||||||
"destination": "المدير",
|
|
||||||
"description": "البيانات التي يعرضها النظام للمدير لمراجعة الطلبات."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "IO-05",
|
|
||||||
"type": "output",
|
|
||||||
"name": "التقرير الشهري",
|
|
||||||
"source": "النظام",
|
|
||||||
"destination": "المدير",
|
|
||||||
"description": "التقرير الذي يولده النظام شهرياً."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "IO-06",
|
|
||||||
"type": "input",
|
|
||||||
"name": "تفاصيل طلب الخدمة (وصف، معلومات الاتصال)",
|
|
||||||
"source": "العميل",
|
|
||||||
"destination": "النظام",
|
|
||||||
"description": "المعلومات التي يقدمها العميل لإنشاء طلب خدمة."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "IO-07",
|
|
||||||
"type": "output",
|
|
||||||
"name": "تأكيد تقديم الطلب",
|
|
||||||
"source": "النظام",
|
|
||||||
"destination": "العميل",
|
|
||||||
"description": "رسالة تأكيد للعميل بعد تقديم طلب الخدمة."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isComplete": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "assistant",
|
|
||||||
"payload": {
|
|
||||||
"reply": "تم اعتماد الهيكل الأولي: 4 Actors، 4 Use Cases، و7 Inputs/Outputs. أصبح لدينا أساس جيد للتقدير؛ هل تريد تثبيت التقدير الحالي وتوليد التقرير أم إضافة تفاصيل تقنية أخرى؟",
|
|
||||||
"stage": "evaluation",
|
|
||||||
"scope": "نظام لفحص واجهة الشات يسمح للمستخدمين بتسجيل الدخول، للمديرين بمراجعة الطلبات، وللعملاء بإرسال طلبات الخدمة، مع إصدار النظام لتقارير شهرية.",
|
|
||||||
"actors": [
|
|
||||||
"المستخدم",
|
|
||||||
"المدير",
|
|
||||||
"العميل",
|
|
||||||
"النظام"
|
|
||||||
],
|
|
||||||
"functionPoints": [
|
|
||||||
{
|
|
||||||
"id": "FP-01",
|
|
||||||
"name": "تسجيل الدخول",
|
|
||||||
"description": "تغطي حالة الاستخدام \"تسجيل الدخول\" للفاعل المستخدم.",
|
|
||||||
"complexity": "معقدة",
|
|
||||||
"fpScore": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "FP-02",
|
|
||||||
"name": "مراجعة الطلبات",
|
|
||||||
"description": "تغطي حالة الاستخدام \"مراجعة الطلبات\" للفاعل المدير.",
|
|
||||||
"complexity": "متوسطة",
|
|
||||||
"fpScore": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "FP-03",
|
|
||||||
"name": "إصدار تقرير شهري",
|
|
||||||
"description": "تغطي حالة الاستخدام \"إصدار تقرير شهري\" للفاعل النظام.",
|
|
||||||
"complexity": "معقدة",
|
|
||||||
"fpScore": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "FP-04",
|
|
||||||
"name": "إرسال طلب خدمة",
|
|
||||||
"description": "تغطي حالة الاستخدام \"إرسال طلب خدمة\" للفاعل العميل.",
|
|
||||||
"complexity": "متوسطة",
|
|
||||||
"fpScore": 4
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"useCases": [
|
|
||||||
{
|
|
||||||
"id": "UC-01",
|
|
||||||
"title": "تسجيل الدخول",
|
|
||||||
"actor": "المستخدم",
|
|
||||||
"preconditions": "يجب أن يكون لدى المستخدم حساب صالح.",
|
|
||||||
"mainFlow": [
|
|
||||||
"يقوم المستخدم بإدخال اسم المستخدم وكلمة المرور.",
|
|
||||||
"يقوم النظام بالتحقق من صحة بيانات الاعتماد.",
|
|
||||||
"إذا كانت البيانات صحيحة، يتم تسجيل دخول المستخدم بنجاح.",
|
|
||||||
"إذا كانت البيانات غير صحيحة، يتم عرض رسالة خطأ."
|
|
||||||
],
|
|
||||||
"alternateFlow": [
|
|
||||||
"في حالة نسيان كلمة المرور، يمكن للمستخدم طلب إعادة تعيينها."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "UC-02",
|
|
||||||
"title": "مراجعة الطلبات",
|
|
||||||
"actor": "المدير",
|
|
||||||
"preconditions": "يجب أن يكون لدى المدير صلاحيات الوصول إلى قسم مراجعة الطلبات.",
|
|
||||||
"mainFlow": [
|
|
||||||
"يقوم المدير بالوصول إلى قسم مراجعة الطلبات.",
|
|
||||||
"يعرض النظام قائمة بالطلبات المقدمة.",
|
|
||||||
"يمكن للمدير اختيار طلب معين لعرض تفاصيله.",
|
|
||||||
"يمكن للمدير تحديث حالة الطلب (مثل: قيد المعالجة، مكتمل، مرفوض)."
|
|
||||||
],
|
|
||||||
"alternateFlow": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "UC-03",
|
|
||||||
"title": "إصدار تقرير شهري",
|
|
||||||
"actor": "النظام",
|
|
||||||
"preconditions": "يجب أن يكون النظام قادراً على الوصول إلى بيانات الطلبات.",
|
|
||||||
"mainFlow": [
|
|
||||||
"في نهاية كل شهر، يقوم النظام بتشغيل عملية إنشاء التقرير.",
|
|
||||||
"يقوم النظام بتجميع بيانات الطلبات خلال الشهر.",
|
|
||||||
"يقوم النظام بإنشاء تقرير شهري يتضمن ملخصاً للطلبات وحالاتها."
|
|
||||||
],
|
|
||||||
"alternateFlow": [
|
|
||||||
"يمكن للمدير طلب إنشاء تقرير في أي وقت."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "UC-04",
|
|
||||||
"title": "إرسال طلب خدمة",
|
|
||||||
"actor": "العميل",
|
|
||||||
"preconditions": "يجب أن يكون لدى العميل حساب أو القدرة على تقديم طلب كضيف.",
|
|
||||||
"mainFlow": [
|
|
||||||
"يقوم العميل بالوصول إلى واجهة تقديم طلب الخدمة.",
|
|
||||||
"يقوم العميل بإدخال تفاصيل الطلب (مثل: وصف الخدمة المطلوبة، معلومات الاتصال).",
|
|
||||||
"يقوم العميل بإرسال الطلب.",
|
|
||||||
"يقوم النظام بتسجيل الطلب وتعيين معرف فريد له.",
|
|
||||||
"يعرض النظام رسالة تأكيد للعميل."
|
|
||||||
],
|
|
||||||
"alternateFlow": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"inputOutputs": [
|
|
||||||
{
|
|
||||||
"id": "IO-01",
|
|
||||||
"type": "input",
|
|
||||||
"name": "بيانات تسجيل الدخول (اسم المستخدم، كلمة المرور)",
|
|
||||||
"source": "المستخدم",
|
|
||||||
"destination": "النظام",
|
|
||||||
"description": "المعلومات التي يدخلها المستخدم لتسجيل الدخول."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "IO-02",
|
|
||||||
"type": "output",
|
|
||||||
"name": "تأكيد تسجيل الدخول / رسالة خطأ",
|
|
||||||
"source": "النظام",
|
|
||||||
"destination": "المستخدم",
|
|
||||||
"description": "نتيجة عملية تسجيل الدخول."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "IO-03",
|
|
||||||
"type": "input",
|
|
||||||
"name": "معرف الطلب / تحديث الحالة",
|
|
||||||
"source": "المدير",
|
|
||||||
"destination": "النظام",
|
|
||||||
"description": "المعلومات التي يدخلها المدير لتحديث حالة الطلب أو مراجعته."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "IO-04",
|
|
||||||
"type": "output",
|
|
||||||
"name": "قائمة الطلبات / تفاصيل الطلب",
|
|
||||||
"source": "النظام",
|
|
||||||
"destination": "المدير",
|
|
||||||
"description": "البيانات التي يعرضها النظام للمدير لمراجعة الطلبات."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "IO-05",
|
|
||||||
"type": "output",
|
|
||||||
"name": "التقرير الشهري",
|
|
||||||
"source": "النظام",
|
|
||||||
"destination": "المدير",
|
|
||||||
"description": "التقرير الذي يولده النظام شهرياً."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "IO-06",
|
|
||||||
"type": "input",
|
|
||||||
"name": "تفاصيل طلب الخدمة (وصف، معلومات الاتصال)",
|
|
||||||
"source": "العميل",
|
|
||||||
"destination": "النظام",
|
|
||||||
"description": "المعلومات التي يقدمها العميل لإنشاء طلب خدمة."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "IO-07",
|
|
||||||
"type": "output",
|
|
||||||
"name": "تأكيد تقديم الطلب",
|
|
||||||
"source": "النظام",
|
|
||||||
"destination": "العميل",
|
|
||||||
"description": "رسالة تأكيد للعميل بعد تقديم طلب الخدمة."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isComplete": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"srsDraft": {
|
|
||||||
"summary": "نظام لفحص واجهة الشات يسمح للمستخدمين بتسجيل الدخول، للمديرين بمراجعة الطلبات، وللعملاء بإرسال طلبات الخدمة، مع إصدار النظام لتقارير شهرية.",
|
|
||||||
"actors": [
|
|
||||||
"المستخدم",
|
|
||||||
"المدير",
|
|
||||||
"العميل",
|
|
||||||
"النظام"
|
|
||||||
],
|
|
||||||
"useCases": [
|
|
||||||
{
|
|
||||||
"id": "UC-01",
|
|
||||||
"title": "تسجيل الدخول",
|
|
||||||
"actor": "المستخدم",
|
|
||||||
"preconditions": "يجب أن يكون لدى المستخدم حساب صالح.",
|
|
||||||
"mainFlow": [
|
|
||||||
"يقوم المستخدم بإدخال اسم المستخدم وكلمة المرور.",
|
|
||||||
"يقوم النظام بالتحقق من صحة بيانات الاعتماد.",
|
|
||||||
"إذا كانت البيانات صحيحة، يتم تسجيل دخول المستخدم بنجاح.",
|
|
||||||
"إذا كانت البيانات غير صحيحة، يتم عرض رسالة خطأ."
|
|
||||||
],
|
|
||||||
"alternateFlow": [
|
|
||||||
"في حالة نسيان كلمة المرور، يمكن للمستخدم طلب إعادة تعيينها."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "UC-02",
|
|
||||||
"title": "مراجعة الطلبات",
|
|
||||||
"actor": "المدير",
|
|
||||||
"preconditions": "يجب أن يكون لدى المدير صلاحيات الوصول إلى قسم مراجعة الطلبات.",
|
|
||||||
"mainFlow": [
|
|
||||||
"يقوم المدير بالوصول إلى قسم مراجعة الطلبات.",
|
|
||||||
"يعرض النظام قائمة بالطلبات المقدمة.",
|
|
||||||
"يمكن للمدير اختيار طلب معين لعرض تفاصيله.",
|
|
||||||
"يمكن للمدير تحديث حالة الطلب (مثل: قيد المعالجة، مكتمل، مرفوض)."
|
|
||||||
],
|
|
||||||
"alternateFlow": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "UC-03",
|
|
||||||
"title": "إصدار تقرير شهري",
|
|
||||||
"actor": "النظام",
|
|
||||||
"preconditions": "يجب أن يكون النظام قادراً على الوصول إلى بيانات الطلبات.",
|
|
||||||
"mainFlow": [
|
|
||||||
"في نهاية كل شهر، يقوم النظام بتشغيل عملية إنشاء التقرير.",
|
|
||||||
"يقوم النظام بتجميع بيانات الطلبات خلال الشهر.",
|
|
||||||
"يقوم النظام بإنشاء تقرير شهري يتضمن ملخصاً للطلبات وحالاتها."
|
|
||||||
],
|
|
||||||
"alternateFlow": [
|
|
||||||
"يمكن للمدير طلب إنشاء تقرير في أي وقت."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "UC-04",
|
|
||||||
"title": "إرسال طلب خدمة",
|
|
||||||
"actor": "العميل",
|
|
||||||
"preconditions": "يجب أن يكون لدى العميل حساب أو القدرة على تقديم طلب كضيف.",
|
|
||||||
"mainFlow": [
|
|
||||||
"يقوم العميل بالوصول إلى واجهة تقديم طلب الخدمة.",
|
|
||||||
"يقوم العميل بإدخال تفاصيل الطلب (مثل: وصف الخدمة المطلوبة، معلومات الاتصال).",
|
|
||||||
"يقوم العميل بإرسال الطلب.",
|
|
||||||
"يقوم النظام بتسجيل الطلب وتعيين معرف فريد له.",
|
|
||||||
"يعرض النظام رسالة تأكيد للعميل."
|
|
||||||
],
|
|
||||||
"alternateFlow": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"inputOutputs": [
|
|
||||||
{
|
|
||||||
"id": "IO-01",
|
|
||||||
"type": "input",
|
|
||||||
"name": "بيانات تسجيل الدخول (اسم المستخدم، كلمة المرور)",
|
|
||||||
"source": "المستخدم",
|
|
||||||
"destination": "النظام",
|
|
||||||
"description": "المعلومات التي يدخلها المستخدم لتسجيل الدخول."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "IO-02",
|
|
||||||
"type": "output",
|
|
||||||
"name": "تأكيد تسجيل الدخول / رسالة خطأ",
|
|
||||||
"source": "النظام",
|
|
||||||
"destination": "المستخدم",
|
|
||||||
"description": "نتيجة عملية تسجيل الدخول."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "IO-03",
|
|
||||||
"type": "input",
|
|
||||||
"name": "معرف الطلب / تحديث الحالة",
|
|
||||||
"source": "المدير",
|
|
||||||
"destination": "النظام",
|
|
||||||
"description": "المعلومات التي يدخلها المدير لتحديث حالة الطلب أو مراجعته."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "IO-04",
|
|
||||||
"type": "output",
|
|
||||||
"name": "قائمة الطلبات / تفاصيل الطلب",
|
|
||||||
"source": "النظام",
|
|
||||||
"destination": "المدير",
|
|
||||||
"description": "البيانات التي يعرضها النظام للمدير لمراجعة الطلبات."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "IO-05",
|
|
||||||
"type": "output",
|
|
||||||
"name": "التقرير الشهري",
|
|
||||||
"source": "النظام",
|
|
||||||
"destination": "المدير",
|
|
||||||
"description": "التقرير الذي يولده النظام شهرياً."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "IO-06",
|
|
||||||
"type": "input",
|
|
||||||
"name": "تفاصيل طلب الخدمة (وصف، معلومات الاتصال)",
|
|
||||||
"source": "العميل",
|
|
||||||
"destination": "النظام",
|
|
||||||
"description": "المعلومات التي يقدمها العميل لإنشاء طلب خدمة."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "IO-07",
|
|
||||||
"type": "output",
|
|
||||||
"name": "تأكيد تقديم الطلب",
|
|
||||||
"source": "النظام",
|
|
||||||
"destination": "العميل",
|
|
||||||
"description": "رسالة تأكيد للعميل بعد تقديم طلب الخدمة."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"functionPoints": [
|
|
||||||
{
|
|
||||||
"id": "FP-01",
|
|
||||||
"name": "تسجيل الدخول",
|
|
||||||
"description": "تغطي حالة الاستخدام \"تسجيل الدخول\" للفاعل المستخدم.",
|
|
||||||
"complexity": "معقدة",
|
|
||||||
"fpScore": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "FP-02",
|
|
||||||
"name": "مراجعة الطلبات",
|
|
||||||
"description": "تغطي حالة الاستخدام \"مراجعة الطلبات\" للفاعل المدير.",
|
|
||||||
"complexity": "متوسطة",
|
|
||||||
"fpScore": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "FP-03",
|
|
||||||
"name": "إصدار تقرير شهري",
|
|
||||||
"description": "تغطي حالة الاستخدام \"إصدار تقرير شهري\" للفاعل النظام.",
|
|
||||||
"complexity": "معقدة",
|
|
||||||
"fpScore": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "FP-04",
|
|
||||||
"name": "إرسال طلب خدمة",
|
|
||||||
"description": "تغطي حالة الاستخدام \"إرسال طلب خدمة\" للفاعل العميل.",
|
|
||||||
"complexity": "متوسطة",
|
|
||||||
"fpScore": 4
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"confidence": "high",
|
|
||||||
"question": "وجدت 4 Actors و4 Use Cases و3 Inputs و4 Outputs. هل تريد اعتمادها أم تعديلها؟",
|
|
||||||
"counts": {
|
|
||||||
"actors": 4,
|
|
||||||
"useCases": 4,
|
|
||||||
"inputs": 3,
|
|
||||||
"outputs": 4
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"needsStructureConfirmation": false,
|
|
||||||
"isComplete": false,
|
|
||||||
"createdAt": "T08:50:59.345Z",
|
|
||||||
"updatedAt": "T08:51:08.433Z",
|
|
||||||
"stage": "evaluation"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
343
public/app.js
343
public/app.js
@ -4,10 +4,6 @@ const state = {
|
|||||||
sessionId: null,
|
sessionId: null,
|
||||||
current: null,
|
current: null,
|
||||||
drafts: [],
|
drafts: [],
|
||||||
ui: {
|
|
||||||
workbenchOpen: false,
|
|
||||||
activeTab: 'srs',
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const stageLabels = {
|
const stageLabels = {
|
||||||
@ -18,15 +14,6 @@ const stageLabels = {
|
|||||||
done: 'مكتمل',
|
done: 'مكتمل',
|
||||||
};
|
};
|
||||||
|
|
||||||
const textareaLimits = {
|
|
||||||
input: { min: 46, max: 180 },
|
|
||||||
'srs-text': { min: 92, max: 240 },
|
|
||||||
'actors-editor': { min: 72, max: 180 },
|
|
||||||
'usecases-editor': { min: 72, max: 200 },
|
|
||||||
'io-editor': { min: 88, max: 220 },
|
|
||||||
'scope-editor': { min: 72, max: 180 },
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', init);
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
@ -39,268 +26,31 @@ function init() {
|
|||||||
sendMessage();
|
sendMessage();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
$('input').addEventListener('input', autoGrow);
|
||||||
['input', 'srs-text', 'actors-editor', 'usecases-editor', 'io-editor', 'scope-editor'].forEach((id) => {
|
$('srs-text').addEventListener('input', autoGrow);
|
||||||
$(id).addEventListener('input', autoGrow);
|
$('actors-editor').addEventListener('input', autoGrow);
|
||||||
});
|
$('usecases-editor').addEventListener('input', autoGrow);
|
||||||
|
$('io-editor').addEventListener('input', autoGrow);
|
||||||
|
$('scope-editor').addEventListener('input', autoGrow);
|
||||||
$('analyze-srs-btn').addEventListener('click', analyzeSrs);
|
$('analyze-srs-btn').addEventListener('click', analyzeSrs);
|
||||||
$('confirm-srs-btn').addEventListener('click', confirmStructure);
|
$('confirm-srs-btn').addEventListener('click', confirmStructure);
|
||||||
$('save-planning-btn').addEventListener('click', savePlanning);
|
$('save-planning-btn').addEventListener('click', savePlanning);
|
||||||
$('export-btn').addEventListener('click', () => {
|
$('export-btn').addEventListener('click', () => {
|
||||||
if (state.sessionId) window.open(`/report/${state.sessionId}`, '_blank');
|
if (state.sessionId) window.open(`/report/${state.sessionId}`, '_blank');
|
||||||
});
|
});
|
||||||
$('srs-file').addEventListener('change', updateSrsFileSelection);
|
$('srs-file').addEventListener('change', () => {
|
||||||
configurePdfEngine();
|
const file = $('srs-file').files?.[0];
|
||||||
updateSrsFileSelection();
|
$('srs-file-name').textContent = file ? `الملف المختار: ${file.name}` : 'لم يتم اختيار ملف بعد.';
|
||||||
$('workspace-collapse').addEventListener('click', () => setWorkbenchOpen(!state.ui.workbenchOpen));
|
|
||||||
document.querySelectorAll('[data-workspace-tab]').forEach((button) => {
|
|
||||||
button.addEventListener('click', () => setWorkbenchTab(button.dataset.workspaceTab, true));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setWorkbenchTab('srs');
|
|
||||||
setWorkbenchOpen(false);
|
|
||||||
resizeAllTextareas();
|
|
||||||
loadDrafts();
|
loadDrafts();
|
||||||
}
|
}
|
||||||
|
|
||||||
function configurePdfEngine() {
|
|
||||||
if (window.pdfjsLib?.GlobalWorkerOptions) {
|
|
||||||
window.pdfjsLib.GlobalWorkerOptions.workerSrc = '/vendor/pdfjs/pdf.worker.min.js';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSelectedSrsFile() {
|
|
||||||
return $('srs-file').files?.[0] || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPdfFile(file) {
|
|
||||||
return Boolean(file) && (/\.pdf$/i.test(file.name || '') || (file.type || '').toLowerCase() === 'application/pdf');
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateSrsFileSelection(statusText = '') {
|
|
||||||
const file = getSelectedSrsFile();
|
|
||||||
const fileName = $('srs-file-name');
|
|
||||||
const note = $('srs-source-note');
|
|
||||||
|
|
||||||
if (statusText) {
|
|
||||||
fileName.textContent = statusText;
|
|
||||||
} else if (file) {
|
|
||||||
fileName.textContent = `الملف المختار: ${file.name}${isPdfFile(file) ? ' — PDF' : ''}`;
|
|
||||||
} else {
|
|
||||||
fileName.textContent = 'لم يتم اختيار ملف بعد.';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (note) {
|
|
||||||
note.textContent = file && isPdfFile(file)
|
|
||||||
? 'سيتم استخراج النص من ملف PDF قبل التحليل. إذا كان الملف صورة ممسوحة ضوئياً فقد تحتاج إلى OCR أو لصق النص يدوياً.'
|
|
||||||
: 'يدعم TXT / MD / CSV / JSON / SRS / REQ إضافةً إلى PDF النصي. يمكنك أيضاً لصق المتطلبات مباشرة.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setSrsProcessingState(isBusy, statusText = '') {
|
|
||||||
const button = $('analyze-srs-btn');
|
|
||||||
button.disabled = Boolean(isBusy);
|
|
||||||
button.textContent = isBusy ? 'جارٍ تحليل المستند...' : 'حلّل الملف/النص';
|
|
||||||
updateSrsFileSelection(statusText);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function readSrsSource({ file, manualText }) {
|
|
||||||
if (file) {
|
|
||||||
if (isPdfFile(file)) {
|
|
||||||
updateSrsFileSelection(`جارٍ استخراج النص من PDF: ${file.name}...`);
|
|
||||||
const content = await extractPdfText(file, ({ currentPage, totalPages }) => {
|
|
||||||
updateSrsFileSelection(`جارٍ استخراج النص من PDF: الصفحة ${currentPage}/${totalPages} — ${file.name}`);
|
|
||||||
});
|
|
||||||
if (!content.trim()) {
|
|
||||||
throw new Error('تعذر استخراج نص واضح من ملف PDF. إذا كان الملف صورة ممسوحة ضوئياً فاستخدم OCR أو الصق النص يدوياً.');
|
|
||||||
}
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = await file.text();
|
|
||||||
if (looksBinary(content)) {
|
|
||||||
throw new Error('الملف المختار غير نصي مقروء. استخدم PDF نصي أو ملف TXT/MD/CSV/JSON، أو الصق النص يدوياً.');
|
|
||||||
}
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
return String(manualText || '').trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function extractPdfText(file, onProgress) {
|
|
||||||
const pdfjs = window.pdfjsLib;
|
|
||||||
if (!pdfjs?.getDocument) {
|
|
||||||
throw new Error('محرّك قراءة PDF غير جاهز حالياً. حدّث الصفحة ثم جرّب مرة أخرى.');
|
|
||||||
}
|
|
||||||
|
|
||||||
pdfjs.GlobalWorkerOptions.workerSrc = '/vendor/pdfjs/pdf.worker.min.js';
|
|
||||||
const data = await file.arrayBuffer();
|
|
||||||
const pdf = await pdfjs.getDocument({ data }).promise;
|
|
||||||
const pages = [];
|
|
||||||
|
|
||||||
for (let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber += 1) {
|
|
||||||
if (typeof onProgress === 'function') {
|
|
||||||
onProgress({ currentPage: pageNumber, totalPages: pdf.numPages });
|
|
||||||
}
|
|
||||||
|
|
||||||
const page = await pdf.getPage(pageNumber);
|
|
||||||
const textContent = await page.getTextContent();
|
|
||||||
const pageText = extractPdfPageText(textContent);
|
|
||||||
if (pageText) pages.push(pageText);
|
|
||||||
}
|
|
||||||
|
|
||||||
return pages.join('\n\n').replace(/\n{3,}/g, '\n\n').trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractPdfPageText(textContent) {
|
|
||||||
const lines = [];
|
|
||||||
let currentLine = [];
|
|
||||||
let lastY = null;
|
|
||||||
|
|
||||||
const flushLine = () => {
|
|
||||||
const line = currentLine.join(' ').replace(/\s+/g, ' ').trim();
|
|
||||||
if (line) lines.push(line);
|
|
||||||
currentLine = [];
|
|
||||||
};
|
|
||||||
|
|
||||||
(textContent?.items || []).forEach((item) => {
|
|
||||||
const raw = String(item?.str ?? '').replace(/\s+/g, ' ').trim();
|
|
||||||
const y = Number(item?.transform?.[5] ?? lastY ?? 0);
|
|
||||||
|
|
||||||
if (lastY !== null && Math.abs(y - lastY) > 4) {
|
|
||||||
flushLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (raw) {
|
|
||||||
currentLine.push(raw);
|
|
||||||
lastY = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item?.hasEOL) {
|
|
||||||
flushLine();
|
|
||||||
lastY = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
flushLine();
|
|
||||||
return lines.join('\n').trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
function autoGrow(event) {
|
function autoGrow(event) {
|
||||||
resizeTextarea(event.target);
|
const target = event.target;
|
||||||
}
|
|
||||||
|
|
||||||
function resizeTextarea(target) {
|
|
||||||
if (!target || target.tagName !== 'TEXTAREA') return;
|
if (!target || target.tagName !== 'TEXTAREA') return;
|
||||||
const limits = textareaLimits[target.id] || { min: 72, max: 180 };
|
target.style.height = 'auto';
|
||||||
|
target.style.height = Math.min(target.scrollHeight, target.classList.contains('editor-textarea') ? 220 : 160) + 'px';
|
||||||
if (target.offsetParent === null) {
|
|
||||||
target.style.height = `${limits.min}px`;
|
|
||||||
target.style.overflowY = 'hidden';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
target.style.height = '0px';
|
|
||||||
const nextHeight = Math.max(limits.min, Math.min(target.scrollHeight, limits.max));
|
|
||||||
target.style.height = `${nextHeight}px`;
|
|
||||||
target.style.overflowY = target.scrollHeight > limits.max ? 'auto' : 'hidden';
|
|
||||||
}
|
|
||||||
|
|
||||||
function resizeAllTextareas() {
|
|
||||||
document.querySelectorAll('textarea').forEach((textarea) => resizeTextarea(textarea));
|
|
||||||
}
|
|
||||||
|
|
||||||
function setWorkbenchOpen(open, options = {}) {
|
|
||||||
const { activeTab, focusChat = false } = options;
|
|
||||||
if (activeTab) setWorkbenchTab(activeTab);
|
|
||||||
|
|
||||||
state.ui.workbenchOpen = Boolean(open);
|
|
||||||
const body = $('workspace-body');
|
|
||||||
const panel = $('workspace-panel');
|
|
||||||
const toggle = $('workspace-collapse');
|
|
||||||
const toggleText = $('workspace-collapse-text');
|
|
||||||
const stateBadge = $('workspace-state');
|
|
||||||
|
|
||||||
panel.classList.toggle('collapsed', !state.ui.workbenchOpen);
|
|
||||||
body.classList.toggle('hidden', !state.ui.workbenchOpen);
|
|
||||||
toggle.setAttribute('aria-expanded', state.ui.workbenchOpen ? 'true' : 'false');
|
|
||||||
toggleText.textContent = state.ui.workbenchOpen ? 'إخفاء اللوحة' : 'فتح اللوحة';
|
|
||||||
stateBadge.textContent = state.ui.workbenchOpen ? 'مفتوحة' : 'مطوية';
|
|
||||||
|
|
||||||
updateWorkbenchSummary(state.current);
|
|
||||||
|
|
||||||
if (state.ui.workbenchOpen) {
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
resizeAllTextareas();
|
|
||||||
focusWorkbenchField();
|
|
||||||
});
|
|
||||||
} else if (focusChat) {
|
|
||||||
requestAnimationFrame(() => $('input').focus());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setWorkbenchTab(tab, openIfCollapsed = false) {
|
|
||||||
state.ui.activeTab = tab === 'proposal' ? 'proposal' : 'srs';
|
|
||||||
|
|
||||||
document.querySelectorAll('[data-workspace-tab]').forEach((button) => {
|
|
||||||
const active = button.dataset.workspaceTab === state.ui.activeTab;
|
|
||||||
button.classList.toggle('active', active);
|
|
||||||
button.setAttribute('aria-selected', active ? 'true' : 'false');
|
|
||||||
});
|
|
||||||
|
|
||||||
document.querySelectorAll('.workspace-card').forEach((panel) => {
|
|
||||||
const active = panel.dataset.panel === state.ui.activeTab;
|
|
||||||
panel.classList.toggle('active', active);
|
|
||||||
panel.hidden = !active;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (openIfCollapsed && !state.ui.workbenchOpen) {
|
|
||||||
setWorkbenchOpen(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.ui.workbenchOpen) {
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
resizeAllTextareas();
|
|
||||||
focusWorkbenchField();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function focusWorkbenchField() {
|
|
||||||
const targetId = state.ui.activeTab === 'proposal' ? 'actors-editor' : 'srs-text';
|
|
||||||
$(targetId)?.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateWorkbenchSummary(data = state.current) {
|
|
||||||
const summaryEl = $('workspace-summary');
|
|
||||||
if (!summaryEl) return;
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
summaryEl.textContent = 'ارفع SRS أو راجع الاقتراح من هنا بدون أن تزاحم مساحة الشات.';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const source = data.srsDraft || data;
|
|
||||||
const counts = source.counts || {
|
|
||||||
actors: (source.actors || []).length,
|
|
||||||
useCases: (source.useCases || []).length,
|
|
||||||
inputs: (source.inputOutputs || []).filter((item) => item.type === 'input').length,
|
|
||||||
outputs: (source.inputOutputs || []).filter((item) => item.type === 'output').length,
|
|
||||||
};
|
|
||||||
|
|
||||||
const parts = [];
|
|
||||||
parts.push(`المرحلة: ${stageLabels[data.stage] || data.stage || '—'}`);
|
|
||||||
|
|
||||||
if (counts.actors || counts.useCases || counts.inputs || counts.outputs) {
|
|
||||||
parts.push(`${counts.actors || 0} Actors · ${counts.useCases || 0} Use Cases · ${counts.inputs || 0} Inputs · ${counts.outputs || 0} Outputs`);
|
|
||||||
} else {
|
|
||||||
parts.push('ابدأ برفع SRS أو لصق المتطلبات لتوليد الاقتراح.');
|
|
||||||
}
|
|
||||||
|
|
||||||
parts.push(data.needsStructureConfirmation ? 'الاقتراح جاهز للمراجعة والاعتماد.' : 'يمكنك إبقاء اللوحة مطوية والتركيز على المحادثة.');
|
|
||||||
summaryEl.textContent = parts.join(' — ');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadDrafts() {
|
async function loadDrafts() {
|
||||||
@ -409,12 +159,9 @@ async function startSession() {
|
|||||||
clearMessages();
|
clearMessages();
|
||||||
hideResumeBanner();
|
hideResumeBanner();
|
||||||
showApp(title);
|
showApp(title);
|
||||||
setWorkbenchTab('srs');
|
|
||||||
setWorkbenchOpen(false);
|
|
||||||
addAgentMessage(data.reply || 'تم بدء الجلسة.', true);
|
addAgentMessage(data.reply || 'تم بدء الجلسة.', true);
|
||||||
updateState(data);
|
updateState(data);
|
||||||
await loadDrafts();
|
await loadDrafts();
|
||||||
resizeAllTextareas();
|
|
||||||
$('input').focus();
|
$('input').focus();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert(`خطأ: ${error.message}`);
|
alert(`خطأ: ${error.message}`);
|
||||||
@ -435,14 +182,8 @@ async function resumeSession(sessionId) {
|
|||||||
showApp(data.title);
|
showApp(data.title);
|
||||||
renderHistory(data.history || []);
|
renderHistory(data.history || []);
|
||||||
updateState(data);
|
updateState(data);
|
||||||
if (data.needsStructureConfirmation) {
|
|
||||||
setWorkbenchOpen(true, { activeTab: 'proposal' });
|
|
||||||
} else {
|
|
||||||
setWorkbenchTab('srs');
|
|
||||||
setWorkbenchOpen(false);
|
|
||||||
$('input').focus();
|
|
||||||
}
|
|
||||||
showResumeBanner(data.resumeMessage || 'أهلاً بك مجدداً، يمكننا متابعة العمل من آخر نقطة توقفنا عندها.');
|
showResumeBanner(data.resumeMessage || 'أهلاً بك مجدداً، يمكننا متابعة العمل من آخر نقطة توقفنا عندها.');
|
||||||
|
$('input').focus();
|
||||||
await loadDrafts();
|
await loadDrafts();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert(`خطأ: ${error.message}`);
|
alert(`خطأ: ${error.message}`);
|
||||||
@ -460,7 +201,7 @@ async function sendMessage() {
|
|||||||
hideResumeBanner();
|
hideResumeBanner();
|
||||||
addUserMessage(text);
|
addUserMessage(text);
|
||||||
input.value = '';
|
input.value = '';
|
||||||
resizeTextarea(input);
|
input.style.height = 'auto';
|
||||||
$('send-btn').disabled = true;
|
$('send-btn').disabled = true;
|
||||||
const thinkingEl = addAgentMessage('يفكّر...', false, true);
|
const thinkingEl = addAgentMessage('يفكّر...', false, true);
|
||||||
|
|
||||||
@ -491,25 +232,31 @@ async function analyzeSrs() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = getSelectedSrsFile();
|
const file = $('srs-file').files?.[0];
|
||||||
const filename = file?.name || '';
|
let content = $('srs-text').value.trim();
|
||||||
let thinkingEl = null;
|
let filename = '';
|
||||||
|
|
||||||
|
if (!content && file) {
|
||||||
|
filename = file.name;
|
||||||
|
content = await file.text();
|
||||||
|
} else if (file) {
|
||||||
|
filename = file.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!content.trim()) {
|
||||||
|
alert('أرفق ملفاً نصياً بسيطاً أو الصق نص المتطلبات أولاً.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (looksBinary(content)) {
|
||||||
|
alert('الملف يبدو غير نصي. في هذه النسخة يُفضَّل رفع ملف نصي بسيط أو لصق محتوى SRS يدوياً.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addUserMessage(filename ? `📄 تم إرسال ملف SRS للتحليل: ${filename}` : '📄 تم إرسال نص SRS للتحليل');
|
||||||
|
const thinkingEl = addAgentMessage('أحلّل المستند وأستخرج Actors وUse Cases وInputs/Outputs...', false, true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setSrsProcessingState(true);
|
|
||||||
const content = await readSrsSource({ file, manualText: $('srs-text').value.trim() });
|
|
||||||
|
|
||||||
if (!content.trim()) {
|
|
||||||
throw new Error('أرفق ملفاً أو الصق نص المتطلبات أولاً.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (looksBinary(content)) {
|
|
||||||
throw new Error('المحتوى المقروء غير صالح للتحليل كنص. استخدم PDF نصي أو ألصق النص مباشرة.');
|
|
||||||
}
|
|
||||||
|
|
||||||
addUserMessage(filename ? `📄 تم إرسال ملف SRS للتحليل: ${filename}` : '📄 تم إرسال نص SRS للتحليل');
|
|
||||||
thinkingEl = addAgentMessage('أحلّل المستند وأستخرج Actors وUse Cases وInputs/Outputs...', false, true);
|
|
||||||
|
|
||||||
const res = await fetch('/api/analyze-srs', {
|
const res = await fetch('/api/analyze-srs', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
@ -518,21 +265,12 @@ async function analyzeSrs() {
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (!res.ok) throw new Error(data.error || 'فشل تحليل المستند');
|
if (!res.ok) throw new Error(data.error || 'فشل تحليل المستند');
|
||||||
thinkingEl.remove();
|
thinkingEl.remove();
|
||||||
thinkingEl = null;
|
|
||||||
addAgentMessage(data.reply || 'تم تحليل المستند.', true);
|
addAgentMessage(data.reply || 'تم تحليل المستند.', true);
|
||||||
updateState(data);
|
updateState(data);
|
||||||
setWorkbenchTab('proposal');
|
|
||||||
setWorkbenchOpen(true);
|
|
||||||
await loadDrafts();
|
await loadDrafts();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (thinkingEl) {
|
thinkingEl.querySelector('.bubble').textContent = `⚠️ ${error.message}`;
|
||||||
thinkingEl.querySelector('.bubble').textContent = `⚠️ ${error.message}`;
|
thinkingEl.classList.remove('thinking');
|
||||||
thinkingEl.classList.remove('thinking');
|
|
||||||
} else {
|
|
||||||
alert(`خطأ: ${error.message}`);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
setSrsProcessingState(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -564,7 +302,6 @@ async function confirmStructure() {
|
|||||||
addAgentMessage(data.reply || 'تم اعتماد الهيكل الأولي.', true);
|
addAgentMessage(data.reply || 'تم اعتماد الهيكل الأولي.', true);
|
||||||
updateState(data);
|
updateState(data);
|
||||||
await loadDrafts();
|
await loadDrafts();
|
||||||
setWorkbenchOpen(false, { focusChat: true });
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
thinkingEl.querySelector('.bubble').textContent = `⚠️ ${error.message}`;
|
thinkingEl.querySelector('.bubble').textContent = `⚠️ ${error.message}`;
|
||||||
thinkingEl.classList.remove('thinking');
|
thinkingEl.classList.remove('thinking');
|
||||||
@ -681,8 +418,6 @@ function updateState(data) {
|
|||||||
fillProposalEditors(data);
|
fillProposalEditors(data);
|
||||||
renderTree(actors, fps, ios);
|
renderTree(actors, fps, ios);
|
||||||
renderPreview(data);
|
renderPreview(data);
|
||||||
updateWorkbenchSummary(data);
|
|
||||||
resizeAllTextareas();
|
|
||||||
|
|
||||||
if (data.isComplete || ucs.length >= 1 || fps.length >= 2) $('export-btn').classList.remove('hidden');
|
if (data.isComplete || ucs.length >= 1 || fps.length >= 2) $('export-btn').classList.remove('hidden');
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@ -111,106 +111,76 @@
|
|||||||
|
|
||||||
<main class="stage">
|
<main class="stage">
|
||||||
<div class="stage-header">
|
<div class="stage-header">
|
||||||
<div class="stage-heading">
|
<div class="stage-title">جلسة تحليل المتطلبات والتقدير</div>
|
||||||
<div class="stage-title">جلسة تحليل المتطلبات والتقدير</div>
|
|
||||||
<div class="stage-subtitle">الشات هو المساحة الأساسية، ولوحة العمل تظهر فقط عند الحاجة.</div>
|
|
||||||
</div>
|
|
||||||
<div class="stage-pill" id="stage-pill">تمهيد</div>
|
<div class="stage-pill" id="stage-pill">تمهيد</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="resume-banner" class="resume-banner glass hidden"></div>
|
<div id="resume-banner" class="resume-banner glass hidden"></div>
|
||||||
|
|
||||||
<section id="workspace-panel" class="workspace-panel glass collapsed">
|
<section class="tool-grid">
|
||||||
<div class="workspace-bar">
|
<div class="tool-card glass">
|
||||||
<div class="workspace-meta">
|
<div class="tool-header">
|
||||||
<div class="workspace-title-row">
|
<div>
|
||||||
<div class="workspace-title">لوحة العمل المدمجة</div>
|
<div class="tool-title">رفع ملف SRS أو نص متطلبات</div>
|
||||||
<span id="workspace-state" class="workspace-state-badge">مطوية</span>
|
<div class="tool-subtitle">يدعم الملفات النصية البسيطة، مع إمكانية اللصق اليدوي.</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="workspace-summary" class="workspace-summary">ارفع SRS أو راجع الاقتراح من هنا بدون أن تزاحم مساحة الشات.</div>
|
<span class="tool-badge">LLM + Heuristic</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="workspace-actions">
|
<div class="tool-actions">
|
||||||
<div class="workspace-tabs" role="tablist" aria-label="لوحة العمل">
|
<label class="file-label" for="srs-file">اختر ملفاً</label>
|
||||||
<button id="workspace-tab-srs" class="workspace-tab active" type="button" data-workspace-tab="srs" aria-selected="true">SRS</button>
|
<input id="srs-file" type="file" accept=".txt,.md,.csv,.json,.srs,.req,.text">
|
||||||
<button id="workspace-tab-proposal" class="workspace-tab" type="button" data-workspace-tab="proposal" aria-selected="false">الاقتراح</button>
|
<button id="analyze-srs-btn" class="btn-secondary">حلّل الملف/النص</button>
|
||||||
</div>
|
|
||||||
<button id="workspace-collapse" class="workspace-collapse-btn" type="button" aria-expanded="false" aria-controls="workspace-body">
|
|
||||||
<span id="workspace-collapse-text">فتح اللوحة</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="srs-file-name" class="hint-line">لم يتم اختيار ملف بعد.</div>
|
||||||
|
<textarea id="srs-text" class="tool-textarea" placeholder="أو الصق هنا مقتطفات SRS / المتطلبات الوظيفية والنطاق العام..." rows="6"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="workspace-body" class="workspace-body hidden">
|
<div class="tool-card glass">
|
||||||
<div class="tool-panels">
|
<div class="tool-header">
|
||||||
<div class="tool-card workspace-card active" data-panel="srs" role="tabpanel" aria-labelledby="workspace-tab-srs">
|
<div>
|
||||||
<div class="tool-header">
|
<div class="tool-title">الاقتراح الأولي القابل للتعديل</div>
|
||||||
<div>
|
<div class="tool-subtitle">عدّل القوائم ثم اضغط تأكيد لاعتماد الهيكل الأولي.</div>
|
||||||
<div class="tool-title">رفع ملف SRS أو نص متطلبات</div>
|
|
||||||
<div class="tool-subtitle">يدعم TXT / MD / CSV / JSON / SRS / REQ إضافةً إلى PDF النصي، مع إمكانية اللصق اليدوي.</div>
|
|
||||||
</div>
|
|
||||||
<span class="tool-badge">LLM + Heuristic</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tool-actions">
|
|
||||||
<label class="file-label" for="srs-file">اختر ملفاً</label>
|
|
||||||
<input id="srs-file" type="file" accept=".pdf,application/pdf,.txt,.md,.csv,.json,.srs,.req,.text">
|
|
||||||
<button id="analyze-srs-btn" class="btn-secondary">حلّل الملف/النص</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="srs-file-name" class="hint-line">لم يتم اختيار ملف بعد.</div>
|
|
||||||
<div id="srs-source-note" class="hint-line">يدعم PDF النصي أيضاً. إذا كان الملف عبارة عن صور ممسوحة ضوئياً فستحتاج OCR أو لصق النص يدوياً.</div>
|
|
||||||
<textarea id="srs-text" class="tool-textarea" placeholder="أو الصق هنا مقتطفات SRS / المتطلبات الوظيفية والنطاق العام..." rows="4"></textarea>
|
|
||||||
</div>
|
</div>
|
||||||
|
<span id="proposal-status" class="tool-badge dim">بانتظار تحليل</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="tool-card workspace-card" data-panel="proposal" role="tabpanel" aria-labelledby="workspace-tab-proposal" hidden>
|
<div id="proposal-summary" class="hint-line">ستظهر هنا أعداد Actors وUse Cases وInputs/Outputs بعد تحليل المستند.</div>
|
||||||
<div class="tool-header">
|
|
||||||
<div>
|
|
||||||
<div class="tool-title">الاقتراح الأولي القابل للتعديل</div>
|
|
||||||
<div class="tool-subtitle">عدّل القوائم ثم اضغط تأكيد لاعتماد الهيكل الأولي.</div>
|
|
||||||
</div>
|
|
||||||
<span id="proposal-status" class="tool-badge dim">بانتظار تحليل</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="proposal-summary" class="hint-line">ستظهر هنا أعداد Actors وUse Cases وInputs/Outputs بعد تحليل المستند.</div>
|
<div class="proposal-grid">
|
||||||
|
<label class="editor-block">
|
||||||
|
<span>Actors — عنصر واحد في كل سطر</span>
|
||||||
|
<textarea id="actors-editor" class="editor-textarea" placeholder="المستخدم المدير نظام خارجي"></textarea>
|
||||||
|
</label>
|
||||||
|
<label class="editor-block">
|
||||||
|
<span>Use Cases — الصيغة: العنوان | الفاعل</span>
|
||||||
|
<textarea id="usecases-editor" class="editor-textarea" placeholder="تسجيل طلب جديد | العميل اعتماد الطلب | المدير"></textarea>
|
||||||
|
</label>
|
||||||
|
<label class="editor-block wide">
|
||||||
|
<span>Inputs / Outputs — الصيغة: Input أو Output | الاسم | المصدر | الوجهة | الوصف</span>
|
||||||
|
<textarea id="io-editor" class="editor-textarea io" placeholder="Input | بيانات الطلب | العميل | النظام | بيانات الطلب الأساسية Output | تقرير شهري | النظام | المدير | تقرير أداء المبيعات"></textarea>
|
||||||
|
</label>
|
||||||
|
<label class="editor-block wide">
|
||||||
|
<span>ملخص النطاق</span>
|
||||||
|
<textarea id="scope-editor" class="editor-textarea" placeholder="ملخص نطاق النظام المتوقع..."></textarea>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="proposal-grid">
|
<div class="tool-actions compact-actions">
|
||||||
<label class="editor-block">
|
<button id="confirm-srs-btn" class="btn-secondary gold">تأكيد واعتماد الاقتراح</button>
|
||||||
<span>Actors — عنصر واحد في كل سطر</span>
|
|
||||||
<textarea id="actors-editor" class="editor-textarea" placeholder="المستخدم المدير نظام خارجي"></textarea>
|
|
||||||
</label>
|
|
||||||
<label class="editor-block">
|
|
||||||
<span>Use Cases — الصيغة: العنوان | الفاعل</span>
|
|
||||||
<textarea id="usecases-editor" class="editor-textarea" placeholder="تسجيل طلب جديد | العميل اعتماد الطلب | المدير"></textarea>
|
|
||||||
</label>
|
|
||||||
<label class="editor-block wide">
|
|
||||||
<span>Inputs / Outputs — الصيغة: Input أو Output | الاسم | المصدر | الوجهة | الوصف</span>
|
|
||||||
<textarea id="io-editor" class="editor-textarea io" placeholder="Input | بيانات الطلب | العميل | النظام | بيانات الطلب الأساسية Output | تقرير شهري | النظام | المدير | تقرير أداء المبيعات"></textarea>
|
|
||||||
</label>
|
|
||||||
<label class="editor-block wide">
|
|
||||||
<span>ملخص النطاق</span>
|
|
||||||
<textarea id="scope-editor" class="editor-textarea" placeholder="ملخص نطاق النظام المتوقع..."></textarea>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tool-actions compact-actions">
|
|
||||||
<button id="confirm-srs-btn" class="btn-secondary gold">تأكيد واعتماد الاقتراح</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="chat-shell glass">
|
<div id="messages" class="messages"></div>
|
||||||
<div id="messages" class="messages"></div>
|
|
||||||
|
|
||||||
<div class="composer">
|
<div class="composer glass">
|
||||||
<textarea id="input" placeholder="اكتب ردك هنا... أو اطلب تعديل المقترحات أو سؤالاً جديداً" rows="1"></textarea>
|
<textarea id="input" placeholder="اكتب ردك هنا... أو اطلب تعديل المقترحات أو سؤالاً جديداً" rows="1"></textarea>
|
||||||
<button id="send-btn" class="send-btn" title="إرسال">
|
<button id="send-btn" class="send-btn" title="إرسال">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12l14-7-7 14-2-5-5-2z"/></svg>
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12l14-7-7 14-2-5-5-2z"/></svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<aside class="preview glass">
|
<aside class="preview glass">
|
||||||
@ -224,7 +194,6 @@
|
|||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/vendor/pdfjs/pdf.min.js"></script>
|
|
||||||
<script src="/app.js"></script>
|
<script src="/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
372
public/style.css
372
public/style.css
@ -371,375 +371,3 @@ body{
|
|||||||
.proposal-grid,.inline-fields,.stat-grid-2,.summary-strip{grid-template-columns:1fr}
|
.proposal-grid,.inline-fields,.stat-grid-2,.summary-strip{grid-template-columns:1fr}
|
||||||
.welcome-card{max-width:100%}
|
.welcome-card{max-width:100%}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* === Focus-mode redesign: larger centered chat + collapsible workbench === */
|
|
||||||
:root{
|
|
||||||
--shell-gap:14px;
|
|
||||||
--sidebar-width:236px;
|
|
||||||
--preview-width:280px;
|
|
||||||
--stage-max:1120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-shell{
|
|
||||||
grid-template-columns:var(--sidebar-width) minmax(0,1.65fr) var(--preview-width);
|
|
||||||
gap:var(--shell-gap);
|
|
||||||
padding:14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar,
|
|
||||||
.preview{
|
|
||||||
padding:18px;
|
|
||||||
border-radius:24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar{gap:2px}
|
|
||||||
.preview{min-width:0}
|
|
||||||
.stage{
|
|
||||||
min-width:0;
|
|
||||||
gap:12px;
|
|
||||||
max-width:var(--stage-max);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-header{margin-bottom:16px}
|
|
||||||
.brand-sub{max-width:160px}
|
|
||||||
.stat-card{padding:14px;margin-bottom:10px;border-radius:16px}
|
|
||||||
.stat-value{font-size:28px}
|
|
||||||
.stat-value.mini{font-size:22px}
|
|
||||||
.tree-section{margin-top:4px}
|
|
||||||
.drafts-sidebar{margin-top:12px}
|
|
||||||
.draft-list{gap:8px}
|
|
||||||
.draft-card{padding:11px 12px;border-radius:12px}
|
|
||||||
.draft-title{font-size:12.5px}
|
|
||||||
.draft-meta,
|
|
||||||
.draft-date,
|
|
||||||
.draft-stats{font-size:10.8px}
|
|
||||||
.btn-export{margin-top:14px;padding:12px;font-size:13px}
|
|
||||||
|
|
||||||
.stage-header{
|
|
||||||
display:flex;
|
|
||||||
align-items:flex-start;
|
|
||||||
justify-content:space-between;
|
|
||||||
gap:12px;
|
|
||||||
padding:0 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stage-heading{
|
|
||||||
display:flex;
|
|
||||||
flex-direction:column;
|
|
||||||
gap:4px;
|
|
||||||
min-width:0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stage-title{font-size:13px;color:var(--pearl-dim);font-weight:600}
|
|
||||||
.stage-subtitle{font-size:12px;line-height:1.7;color:rgba(244,241,234,.7)}
|
|
||||||
.stage-pill{padding:6px 12px;white-space:nowrap}
|
|
||||||
|
|
||||||
.resume-banner{
|
|
||||||
padding:11px 14px;
|
|
||||||
border-radius:18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-panel{
|
|
||||||
padding:14px 16px;
|
|
||||||
display:flex;
|
|
||||||
flex-direction:column;
|
|
||||||
gap:12px;
|
|
||||||
overflow:hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-panel.collapsed{
|
|
||||||
padding-bottom:12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-bar{
|
|
||||||
display:flex;
|
|
||||||
align-items:flex-start;
|
|
||||||
justify-content:space-between;
|
|
||||||
gap:14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-meta{
|
|
||||||
min-width:0;
|
|
||||||
display:flex;
|
|
||||||
flex-direction:column;
|
|
||||||
gap:6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-title-row{
|
|
||||||
display:flex;
|
|
||||||
align-items:center;
|
|
||||||
gap:10px;
|
|
||||||
flex-wrap:wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-title{
|
|
||||||
font-size:13px;
|
|
||||||
font-weight:700;
|
|
||||||
color:var(--pearl);
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-state-badge{
|
|
||||||
display:inline-flex;
|
|
||||||
align-items:center;
|
|
||||||
gap:6px;
|
|
||||||
padding:4px 10px;
|
|
||||||
border-radius:999px;
|
|
||||||
background:rgba(255,255,255,.05);
|
|
||||||
border:1px solid rgba(255,255,255,.08);
|
|
||||||
color:var(--pearl-dim);
|
|
||||||
font-size:10.5px;
|
|
||||||
font-weight:700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-summary{
|
|
||||||
font-size:11.5px;
|
|
||||||
line-height:1.8;
|
|
||||||
color:var(--pearl-dim);
|
|
||||||
max-width:720px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-actions{
|
|
||||||
display:flex;
|
|
||||||
align-items:center;
|
|
||||||
justify-content:flex-end;
|
|
||||||
gap:8px;
|
|
||||||
flex-wrap:wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-tabs{
|
|
||||||
display:inline-flex;
|
|
||||||
align-items:center;
|
|
||||||
gap:6px;
|
|
||||||
padding:4px;
|
|
||||||
background:rgba(0,0,0,.22);
|
|
||||||
border:1px solid rgba(255,255,255,.05);
|
|
||||||
border-radius:999px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-tab,
|
|
||||||
.workspace-collapse-btn{
|
|
||||||
border:none;
|
|
||||||
cursor:pointer;
|
|
||||||
font-family:inherit;
|
|
||||||
transition:.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-tab{
|
|
||||||
min-width:88px;
|
|
||||||
padding:9px 14px;
|
|
||||||
border-radius:999px;
|
|
||||||
background:transparent;
|
|
||||||
color:var(--pearl-dim);
|
|
||||||
font-size:12px;
|
|
||||||
font-weight:700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-tab:hover{
|
|
||||||
color:var(--pearl);
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-tab.active{
|
|
||||||
background:linear-gradient(135deg,rgba(201,168,76,.24),rgba(201,168,76,.14));
|
|
||||||
color:var(--gold-soft);
|
|
||||||
box-shadow:0 8px 22px rgba(0,0,0,.18);
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-collapse-btn{
|
|
||||||
padding:10px 14px;
|
|
||||||
border-radius:14px;
|
|
||||||
background:rgba(255,255,255,.06);
|
|
||||||
color:var(--pearl);
|
|
||||||
border:1px solid rgba(255,255,255,.08);
|
|
||||||
font-size:12px;
|
|
||||||
font-weight:700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-collapse-btn:hover{
|
|
||||||
border-color:rgba(201,168,76,.35);
|
|
||||||
transform:translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-body{
|
|
||||||
max-height:min(40vh, 430px);
|
|
||||||
overflow:auto;
|
|
||||||
padding-top:4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-body.hidden{
|
|
||||||
display:none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-panels{
|
|
||||||
display:flex;
|
|
||||||
flex-direction:column;
|
|
||||||
gap:12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-card{
|
|
||||||
display:none;
|
|
||||||
padding:14px;
|
|
||||||
min-height:0;
|
|
||||||
overflow:auto;
|
|
||||||
border-radius:18px;
|
|
||||||
background:rgba(255,255,255,.025);
|
|
||||||
border:1px solid rgba(255,255,255,.06);
|
|
||||||
box-shadow:inset 0 1px 0 rgba(255,255,255,.03);
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-card.active{
|
|
||||||
display:flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-header{gap:10px}
|
|
||||||
.tool-title{font-size:13.5px}
|
|
||||||
.tool-subtitle{font-size:11.5px}
|
|
||||||
.tool-actions{gap:8px}
|
|
||||||
.tool-actions.compact-actions{justify-content:flex-start}
|
|
||||||
.tool-badge{font-size:10px;padding:6px 9px}
|
|
||||||
.hint-line{margin-top:6px}
|
|
||||||
|
|
||||||
.tool-textarea,
|
|
||||||
.editor-textarea{
|
|
||||||
padding:10px 12px;
|
|
||||||
min-height:72px;
|
|
||||||
resize:none;
|
|
||||||
line-height:1.75;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-textarea{
|
|
||||||
min-height:92px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-textarea.io{
|
|
||||||
min-height:88px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.proposal-grid{gap:10px}
|
|
||||||
.editor-block{gap:5px;font-size:11.5px}
|
|
||||||
.file-label{padding:9px 13px}
|
|
||||||
.btn-secondary{padding:10px 13px}
|
|
||||||
|
|
||||||
.chat-shell{
|
|
||||||
flex:1;
|
|
||||||
min-height:0;
|
|
||||||
display:flex;
|
|
||||||
flex-direction:column;
|
|
||||||
overflow:hidden;
|
|
||||||
padding:0;
|
|
||||||
border-radius:26px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages{
|
|
||||||
flex:1;
|
|
||||||
min-height:0;
|
|
||||||
overflow-y:auto;
|
|
||||||
padding:24px 18px 16px;
|
|
||||||
display:flex;
|
|
||||||
flex-direction:column;
|
|
||||||
gap:18px;
|
|
||||||
background:transparent;
|
|
||||||
border:none;
|
|
||||||
border-radius:0;
|
|
||||||
scroll-behavior:smooth;
|
|
||||||
}
|
|
||||||
|
|
||||||
.msg{gap:12px}
|
|
||||||
.avatar{width:32px;height:32px;font-size:12px}
|
|
||||||
.bubble{
|
|
||||||
max-width:min(86%, 900px);
|
|
||||||
padding:15px 18px;
|
|
||||||
line-height:1.95;
|
|
||||||
font-size:15px;
|
|
||||||
border-radius:18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.msg.agent .bubble{
|
|
||||||
background:rgba(255,255,255,.02);
|
|
||||||
border:1px solid rgba(255,255,255,.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.msg.user .bubble{
|
|
||||||
background:rgba(124,92,255,.1);
|
|
||||||
border:1px solid rgba(124,92,255,.18);
|
|
||||||
}
|
|
||||||
|
|
||||||
.composer{
|
|
||||||
display:flex;
|
|
||||||
align-items:flex-end;
|
|
||||||
gap:12px;
|
|
||||||
padding:12px 16px 14px;
|
|
||||||
border-top:1px solid rgba(255,255,255,.06);
|
|
||||||
background:rgba(255,255,255,.025);
|
|
||||||
}
|
|
||||||
|
|
||||||
#input{
|
|
||||||
padding:8px 0;
|
|
||||||
font-size:15px;
|
|
||||||
line-height:1.75;
|
|
||||||
max-height:180px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.send-btn{width:44px;height:44px}
|
|
||||||
|
|
||||||
.preview-header{margin-bottom:12px}
|
|
||||||
.preview-content{font-size:12px;line-height:1.8}
|
|
||||||
.summary-strip{gap:8px}
|
|
||||||
.preview-metric{padding:11px}
|
|
||||||
.metric-value{font-size:20px}
|
|
||||||
.io-prev,
|
|
||||||
.preview-content .uc-prev,
|
|
||||||
.analysis-card,
|
|
||||||
.sprint-card,
|
|
||||||
.milestone-item{border-radius:10px}
|
|
||||||
|
|
||||||
@media (max-width:1380px){
|
|
||||||
.app-shell{
|
|
||||||
grid-template-columns:220px minmax(0,1fr) 250px;
|
|
||||||
}
|
|
||||||
.preview,
|
|
||||||
.sidebar{padding:16px}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width:1120px){
|
|
||||||
.app-shell{
|
|
||||||
grid-template-columns:220px minmax(0,1fr);
|
|
||||||
}
|
|
||||||
.preview{display:none}
|
|
||||||
.stage{max-width:none}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width:860px){
|
|
||||||
.app-shell{
|
|
||||||
grid-template-columns:1fr;
|
|
||||||
padding:10px;
|
|
||||||
gap:10px;
|
|
||||||
}
|
|
||||||
.sidebar{display:none}
|
|
||||||
.stage-header{
|
|
||||||
flex-direction:column;
|
|
||||||
align-items:stretch;
|
|
||||||
}
|
|
||||||
.workspace-bar,
|
|
||||||
.workspace-actions{
|
|
||||||
flex-direction:column;
|
|
||||||
align-items:stretch;
|
|
||||||
}
|
|
||||||
.workspace-tabs{
|
|
||||||
width:100%;
|
|
||||||
justify-content:space-between;
|
|
||||||
}
|
|
||||||
.workspace-tab{
|
|
||||||
flex:1;
|
|
||||||
min-width:0;
|
|
||||||
}
|
|
||||||
.workspace-body{
|
|
||||||
max-height:min(44vh, 460px);
|
|
||||||
}
|
|
||||||
.messages{
|
|
||||||
padding:18px 12px 14px;
|
|
||||||
}
|
|
||||||
.bubble{
|
|
||||||
max-width:92%;
|
|
||||||
font-size:14.5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
22
public/vendor/pdfjs/pdf.min.js
vendored
22
public/vendor/pdfjs/pdf.min.js
vendored
File diff suppressed because one or more lines are too long
22
public/vendor/pdfjs/pdf.worker.min.js
vendored
22
public/vendor/pdfjs/pdf.worker.min.js
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user