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",
|
||||
"updatedAt": "T08:15:12.915Z",
|
||||
"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,
|
||||
current: null,
|
||||
drafts: [],
|
||||
ui: {
|
||||
workbenchOpen: false,
|
||||
activeTab: 'srs',
|
||||
},
|
||||
};
|
||||
|
||||
const stageLabels = {
|
||||
@ -18,15 +14,6 @@ const stageLabels = {
|
||||
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);
|
||||
|
||||
function init() {
|
||||
@ -39,268 +26,31 @@ function init() {
|
||||
sendMessage();
|
||||
}
|
||||
});
|
||||
|
||||
['input', 'srs-text', 'actors-editor', 'usecases-editor', 'io-editor', 'scope-editor'].forEach((id) => {
|
||||
$(id).addEventListener('input', autoGrow);
|
||||
});
|
||||
|
||||
$('input').addEventListener('input', autoGrow);
|
||||
$('srs-text').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);
|
||||
$('confirm-srs-btn').addEventListener('click', confirmStructure);
|
||||
$('save-planning-btn').addEventListener('click', savePlanning);
|
||||
$('export-btn').addEventListener('click', () => {
|
||||
if (state.sessionId) window.open(`/report/${state.sessionId}`, '_blank');
|
||||
});
|
||||
$('srs-file').addEventListener('change', updateSrsFileSelection);
|
||||
configurePdfEngine();
|
||||
updateSrsFileSelection();
|
||||
$('workspace-collapse').addEventListener('click', () => setWorkbenchOpen(!state.ui.workbenchOpen));
|
||||
document.querySelectorAll('[data-workspace-tab]').forEach((button) => {
|
||||
button.addEventListener('click', () => setWorkbenchTab(button.dataset.workspaceTab, true));
|
||||
$('srs-file').addEventListener('change', () => {
|
||||
const file = $('srs-file').files?.[0];
|
||||
$('srs-file-name').textContent = file ? `الملف المختار: ${file.name}` : 'لم يتم اختيار ملف بعد.';
|
||||
});
|
||||
|
||||
setWorkbenchTab('srs');
|
||||
setWorkbenchOpen(false);
|
||||
resizeAllTextareas();
|
||||
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) {
|
||||
resizeTextarea(event.target);
|
||||
}
|
||||
|
||||
function resizeTextarea(target) {
|
||||
const target = event.target;
|
||||
if (!target || target.tagName !== 'TEXTAREA') return;
|
||||
const limits = textareaLimits[target.id] || { min: 72, max: 180 };
|
||||
|
||||
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(' — ');
|
||||
target.style.height = 'auto';
|
||||
target.style.height = Math.min(target.scrollHeight, target.classList.contains('editor-textarea') ? 220 : 160) + 'px';
|
||||
}
|
||||
|
||||
async function loadDrafts() {
|
||||
@ -409,12 +159,9 @@ async function startSession() {
|
||||
clearMessages();
|
||||
hideResumeBanner();
|
||||
showApp(title);
|
||||
setWorkbenchTab('srs');
|
||||
setWorkbenchOpen(false);
|
||||
addAgentMessage(data.reply || 'تم بدء الجلسة.', true);
|
||||
updateState(data);
|
||||
await loadDrafts();
|
||||
resizeAllTextareas();
|
||||
$('input').focus();
|
||||
} catch (error) {
|
||||
alert(`خطأ: ${error.message}`);
|
||||
@ -435,14 +182,8 @@ async function resumeSession(sessionId) {
|
||||
showApp(data.title);
|
||||
renderHistory(data.history || []);
|
||||
updateState(data);
|
||||
if (data.needsStructureConfirmation) {
|
||||
setWorkbenchOpen(true, { activeTab: 'proposal' });
|
||||
} else {
|
||||
setWorkbenchTab('srs');
|
||||
setWorkbenchOpen(false);
|
||||
$('input').focus();
|
||||
}
|
||||
showResumeBanner(data.resumeMessage || 'أهلاً بك مجدداً، يمكننا متابعة العمل من آخر نقطة توقفنا عندها.');
|
||||
$('input').focus();
|
||||
await loadDrafts();
|
||||
} catch (error) {
|
||||
alert(`خطأ: ${error.message}`);
|
||||
@ -460,7 +201,7 @@ async function sendMessage() {
|
||||
hideResumeBanner();
|
||||
addUserMessage(text);
|
||||
input.value = '';
|
||||
resizeTextarea(input);
|
||||
input.style.height = 'auto';
|
||||
$('send-btn').disabled = true;
|
||||
const thinkingEl = addAgentMessage('يفكّر...', false, true);
|
||||
|
||||
@ -491,25 +232,31 @@ async function analyzeSrs() {
|
||||
return;
|
||||
}
|
||||
|
||||
const file = getSelectedSrsFile();
|
||||
const filename = file?.name || '';
|
||||
let thinkingEl = null;
|
||||
const file = $('srs-file').files?.[0];
|
||||
let content = $('srs-text').value.trim();
|
||||
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 {
|
||||
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', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@ -518,21 +265,12 @@ async function analyzeSrs() {
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.error || 'فشل تحليل المستند');
|
||||
thinkingEl.remove();
|
||||
thinkingEl = null;
|
||||
addAgentMessage(data.reply || 'تم تحليل المستند.', true);
|
||||
updateState(data);
|
||||
setWorkbenchTab('proposal');
|
||||
setWorkbenchOpen(true);
|
||||
await loadDrafts();
|
||||
} catch (error) {
|
||||
if (thinkingEl) {
|
||||
thinkingEl.querySelector('.bubble').textContent = `⚠️ ${error.message}`;
|
||||
thinkingEl.classList.remove('thinking');
|
||||
} else {
|
||||
alert(`خطأ: ${error.message}`);
|
||||
}
|
||||
} finally {
|
||||
setSrsProcessingState(false);
|
||||
thinkingEl.querySelector('.bubble').textContent = `⚠️ ${error.message}`;
|
||||
thinkingEl.classList.remove('thinking');
|
||||
}
|
||||
}
|
||||
|
||||
@ -564,7 +302,6 @@ async function confirmStructure() {
|
||||
addAgentMessage(data.reply || 'تم اعتماد الهيكل الأولي.', true);
|
||||
updateState(data);
|
||||
await loadDrafts();
|
||||
setWorkbenchOpen(false, { focusChat: true });
|
||||
} catch (error) {
|
||||
thinkingEl.querySelector('.bubble').textContent = `⚠️ ${error.message}`;
|
||||
thinkingEl.classList.remove('thinking');
|
||||
@ -681,8 +418,6 @@ function updateState(data) {
|
||||
fillProposalEditors(data);
|
||||
renderTree(actors, fps, ios);
|
||||
renderPreview(data);
|
||||
updateWorkbenchSummary(data);
|
||||
resizeAllTextareas();
|
||||
|
||||
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">
|
||||
<div class="stage-header">
|
||||
<div class="stage-heading">
|
||||
<div class="stage-title">جلسة تحليل المتطلبات والتقدير</div>
|
||||
<div class="stage-subtitle">الشات هو المساحة الأساسية، ولوحة العمل تظهر فقط عند الحاجة.</div>
|
||||
</div>
|
||||
<div class="stage-title">جلسة تحليل المتطلبات والتقدير</div>
|
||||
<div class="stage-pill" id="stage-pill">تمهيد</div>
|
||||
</div>
|
||||
|
||||
<div id="resume-banner" class="resume-banner glass hidden"></div>
|
||||
|
||||
<section id="workspace-panel" class="workspace-panel glass collapsed">
|
||||
<div class="workspace-bar">
|
||||
<div class="workspace-meta">
|
||||
<div class="workspace-title-row">
|
||||
<div class="workspace-title">لوحة العمل المدمجة</div>
|
||||
<span id="workspace-state" class="workspace-state-badge">مطوية</span>
|
||||
<section class="tool-grid">
|
||||
<div class="tool-card glass">
|
||||
<div class="tool-header">
|
||||
<div>
|
||||
<div class="tool-title">رفع ملف SRS أو نص متطلبات</div>
|
||||
<div class="tool-subtitle">يدعم الملفات النصية البسيطة، مع إمكانية اللصق اليدوي.</div>
|
||||
</div>
|
||||
<div id="workspace-summary" class="workspace-summary">ارفع SRS أو راجع الاقتراح من هنا بدون أن تزاحم مساحة الشات.</div>
|
||||
<span class="tool-badge">LLM + Heuristic</span>
|
||||
</div>
|
||||
|
||||
<div class="workspace-actions">
|
||||
<div class="workspace-tabs" role="tablist" aria-label="لوحة العمل">
|
||||
<button id="workspace-tab-srs" class="workspace-tab active" type="button" data-workspace-tab="srs" aria-selected="true">SRS</button>
|
||||
<button id="workspace-tab-proposal" class="workspace-tab" type="button" data-workspace-tab="proposal" aria-selected="false">الاقتراح</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 class="tool-actions">
|
||||
<label class="file-label" for="srs-file">اختر ملفاً</label>
|
||||
<input id="srs-file" type="file" accept=".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>
|
||||
<textarea id="srs-text" class="tool-textarea" placeholder="أو الصق هنا مقتطفات SRS / المتطلبات الوظيفية والنطاق العام..." rows="6"></textarea>
|
||||
</div>
|
||||
|
||||
<div id="workspace-body" class="workspace-body hidden">
|
||||
<div class="tool-panels">
|
||||
<div class="tool-card workspace-card active" data-panel="srs" role="tabpanel" aria-labelledby="workspace-tab-srs">
|
||||
<div class="tool-header">
|
||||
<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 class="tool-card glass">
|
||||
<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 class="tool-card workspace-card" data-panel="proposal" role="tabpanel" aria-labelledby="workspace-tab-proposal" hidden>
|
||||
<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 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">
|
||||
<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="tool-actions compact-actions">
|
||||
<button id="confirm-srs-btn" class="btn-secondary gold">تأكيد واعتماد الاقتراح</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tool-actions compact-actions">
|
||||
<button id="confirm-srs-btn" class="btn-secondary gold">تأكيد واعتماد الاقتراح</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="chat-shell glass">
|
||||
<div id="messages" class="messages"></div>
|
||||
<div id="messages" class="messages"></div>
|
||||
|
||||
<div class="composer">
|
||||
<textarea id="input" placeholder="اكتب ردك هنا... أو اطلب تعديل المقترحات أو سؤالاً جديداً" rows="1"></textarea>
|
||||
<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>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<div class="composer glass">
|
||||
<textarea id="input" placeholder="اكتب ردك هنا... أو اطلب تعديل المقترحات أو سؤالاً جديداً" rows="1"></textarea>
|
||||
<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>
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<aside class="preview glass">
|
||||
@ -224,7 +194,6 @@
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<script src="/vendor/pdfjs/pdf.min.js"></script>
|
||||
<script src="/app.js"></script>
|
||||
</body>
|
||||
</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}
|
||||
.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