Kính gửi: Đoàn Đại biểu Quốc hội tỉnh Tây Ninh
Bộ Y tế nhận được công văn số 2081/BDN ngày 30/12/2022 của Ban Dân nguyện của Ủy ban Thường vụ Quốc hội về việc chuyển kiến nghị của cử tri gửi tới Quốc hội sau kỳ họp thứ 4, Quốc hội khóa XV, trong đó có một số kiến nghị của cử tri tỉnh Tây Ninh.
Sau khi nghiên cứu nội dung kiến nghị và rà soát các văn bản liên quan, Bộ Y tế xin trả lời đối với từng kiến nghị của cử tri liên quan đến lĩnh vực quản lý của ngành Y tế, cụ thể như sau:
1. Cử tri phản ánh tình trạng thực phẩm kém chất lượng, không rõ xuất xứ tràn lan trên thị trường, sử dụng nhiều hóa chất độc hại khi chế biến thực phẩm, không đảm bảo vệ sinh gây ảnh hưởng đến sức khỏe và quyền lợi của người tiêu dùng. Kiến nghị nghiên cứu, tham mưu Chính phủ trình Quốc hội sửa đổi, bổ sung Luật An toàn thực phẩm nhằm tăng cường hơn nữa công tác quản lý và có biện pháp xử phạt nặng hơn để đảm bảo sức khỏe cho người dân.
Thực hiện Kết luận số 19-KL/TW ngày 14/10/2021 của Bộ Chính trị và Đề án định hướng Chương trình xây dựng pháp luật nhiệm kỳ Quốc hội khóa XV; Kế hoạch số 81/KH-UBTVQH ngày 05/11/2021 của Ủy ban Thường vụ Quốc hội và Quyết định số 2114/QĐ-TTg ngày 16/12/2021 của Thủ tướng Chính phủ ban hành Kế hoạch thực hiện Kết luận số 19-KL/TW của Bộ Chính trị và Đề án định hướng Chương trình xây dựng pháp luật nhiệm kỳ Quốc hội khóa XV; Bộ Y tế đã ban hành Quyết định số 5975/QĐ-BYT ngày 30/12/2021 phê duyệt Kế hoạch xây dựng văn bản quy phạm pháp luật về y tế và giao nhiệm vụ triển khai nghiên cứu, xây dựng dự án Luật an toàn thực phẩm sửa đổi nhằm tăng cường công tác quản lý về an toàn thực phẩm.
Để đảm bảo tiến độ thực hiện dự án Luật an toàn thực phẩm sửa đổi, Bộ Y tế đã ban hành kế hoạch chi tiết triển khai xây dựng Luật an toàn thực phẩm sửa đổi tại Kế hoạch số 1072/KH-BYT ngày 22/8/2022 của Bộ Y tế. Theo đó, Dự án Luật an toàn thực phẩm sửa đổi, bổ sung sẽ trình Chính phủ đề nghị xây dựng năm 2023 và dự kiến trình Quốc hội xem xét, thông qua tại Kỳ họp thứ 8 hoặc kỳ họp thứ 9 tháng 5/2025.
Hiện Bộ Y tế đang tổng hợp và xây dựng báo cáo tổng kết 10 năm thực hiện Luật an toàn thực phẩm.
2. Cử tri phản ánh tình trạng thiếu thuốc bảo hiểm y tế hiện nay rất trầm trọng gây bức xúc trong Nhân dân. Kiến nghị Chính phủ, Bộ Y tế sớm có giải pháp khắc phục tình trạng đấu thầu thuốc để đủ thuốc cung cấp cho người dân khi tham gia khám bệnh bằng bảo hiểm y tế
2.1. Hiện tượng thiếu thuốc, vật tư y tế ở một số cơ sở y tế công lập trong thời gian qua
- Nguyên nhân khách quan: (1) Năm 2021 các cơ sở y tế phải tập trung lực lượng cho công tác phòng, chống dịch COVID-19, tăng khối lượng công việc do dịch bệnh, việc xây dựng, thực hiện kế hoạch mua sắm, đấu thầu bị ảnh hưởng. (2) Nguồn cung nguyên liệu, hàng hóa khan hiếm, giá cả biến động tăng trên quy mô toàn cầu khiến việc mua sắm thuốc, vật tư y tế, hóa chất, các sinh phẩm càng trở nên khó khăn hơn. (3) Sau đại dịch COVID-19, số lượng người dân đi khám, chữa bệnh tăng vọt, vượt quá khả năng cung ứng thuốc, hóa chất, vật tư y tế của các cơ sở y tế. (4) Các hợp đồng cung ứng đã thực hiện những năm trước hết hạn phải chờ kế hoạch đấu thầu mới.
- Nguyên nhân chủ quan: (1) Hạn chế nguồn cung do việc cấp phép, gia hạn giấy phép lưu hành chậm. (2) Có tâm lý e ngại, sợ sai trong tổ chức thực hiện mua sắm, thiếu nhân lực có chuyên môn tổ chức đấu thầu. (3) Tiến độ thực hiện mua sắm thuốc thuộc danh mục đấu thầu tập trung thuốc quốc gia, đàm phán giá và đấu thầu tập trung cấp địa phương còn chậm; nhiều gói thầu số lượng ít không thu hút nhà cung cấp,...(4) Tại một số đơn vị có tình trạng tồn tại công nợ với nhà thầu, do chưa được bảo hiểm xã hội thanh, quyết toán chi phí khám, chữa bệnh một số năm trước đó; một số nhà cung cấp cũng e ngại trong việc cung ứng hàng hóa cho các đơn vị công do liên quan tới giá chưa hợp lý, thủ tục đấu thầu, thanh toán phức tạp; một số nhà thầu không tiếp tục tham dự thầu, không tiếp tục sẵn sàng giao hàng hoặc giao hàng với một số lượng rất hạn chế, chỉ đủ sử dụng trong một thời gian ngắn, gây nên tình trạng thiếu hụt thuốc sử dụng cho người bệnh. (5) Nhiều gói thầu phải thực hiện đấu thầu lần 2, lần 3 vẫn không có kết quả vì không có nhà thầu dự thầu do giá hàng hóa trên thị trường đã biến động, tăng so với giá kế hoạch được lập.
2.2. Các giải pháp đã và đang triển khai để khắc phục tình trạng thiếu thuốc, vật tư y tế để phục vụ chăm sóc sức khỏe nhân dân
2.2.1. Hoàn thiện hành lang pháp lý
Trong thời gian qua dưới sự quan tâm lãnh đạo của Trung ương, Quốc hội, Chính phủ, Thủ tướng Chính phủ đã quyết liệt chỉ đạo Bộ Y tế phối hợp với các bộ, ngành tham mưu để giải quyết triệt để vấn đề thiếu thuốc, vật tư, trang thiết bị y tế. Bộ Y tế cùng các bộ, ngành đã rất nỗ lực và cố gắng tìm giải pháp tháo gỡ khó khăn; chỉ đạo đẩy mạnh việc thực hiện mua sắm, cung ứng đủ thuốc, trang thiết bị y tế, đặc biệt tập trung tháo gỡ những khó khăn về mặt pháp lý, cụ thể:
- Ngày 09/01/2023 trình Quốc hội thông qua Luật Khám bệnh, chữa bệnh sửa đổi số 15/2023/QH15, có hiệu lực kể từ ngày 01/01/2024 với các giải pháp để tăng nguồn lực cho cơ sở khám bệnh, chữa bệnh như các quy định về giá dịch vụ khám bệnh, chữa bệnh, tự chủ hay các quy định về vay vốn, thuê, mượn thiết bị y tế...;
- Báo cáo Quốc hội ban hành Nghị quyết số 80/2023/QH15, trong đó cho phép tiếp tục sử dụng giấy đăng ký lưu hành thuốc, nguyên liệu làm thuốc hết thời hạn hiệu lực trong giai đoạn từ 01/01/2023 đến 31/12/2024 được tiếp tục sử dụng từ ngày hết hiệu lực đến hết ngày 31/12/2024. Với sự cho phép của Nghị quyết 80/2023/QH15, ngay trong tháng 02/2023, Bộ Y tế đã gia hạn giấy phép lưu hành của gần 10.000 thuốc, đảm bảo nguồn cung, tháo gỡ khó khăn cho các doanh nghiệp cung ứng thuốc trên thị trường.
- Trình Chính phủ ban hành Nghị quyết số 144/NQ-CP ngày 05/11/2022 của Chính phủ về việc bảo đảm thuốc, trang thiết bị y tế và thanh toán chi phí khám bệnh, chữa bệnh bảo hiểm y tế.
- Trình Thủ tướng Chính phủ ban hành Công điện số 778/CĐ-TTg ngày 05/9/2022, Công điện số 72/CĐ-TTg ngày 25/02/2023 về việc bảo đảm thuốc, trang thiết bị y tế để phục vụ công tác khám bệnh, chữa bệnh.
- Bộ Y tế ban hành Thông tư số 14/2022/TT-BYT ngày 06/12/2022 về việc bãi bỏ một số văn bản quy phạm pháp luật do Bộ trưởng Bộ Y tế ban hành, liên tịch ban hành; theo đó đã bãi bỏ khoản 3, Điều 8 Thông tư số 14/2020/TT-BYT .
- Tiếp tục hoàn thiện thể chế, các cơ chế chính sách: Sửa đổi Luật Dược 2016 (trình Chính phủ từ tháng 9/2022, đang hoàn thiện hồ sơ trình Quốc hội).
- Trình Chính phủ sửa đổi Nghị định số 146/2018/NĐ-CP về hướng dẫn Luật Bảo hiểm y tế (đã xin ý kiến Thành viên Chính phủ) để giải quyết các vấn đề bất cập liên quan tới bảo hiểm y tế.
- Ban hành Thông tư sửa Thông tư số 15/2019/TT-BYT quy định việc đấu thầu thuốc tại các cơ sở y tế công lập.
Đặc biệt, để tiếp tục tháo gỡ những khó khăn liên quan tới đảm bảo việc cung ứng đầy đủ thuốc, trang thiết bị, vật tư y tế phục vụ công tác bảo vệ, chăm sóc và nâng cao sức khỏe nhân dân, ngay đầu tháng 3/2023, Bộ Y tế đã trình Chính phủ đã ban hành:
- Nghị định số 07/2003/NĐ-CP ngày 03/3/2023 sửa đổi, bổ sung một số điều của Nghị định số 98/2021/NĐ-CP ngày 08/11/2021 về quản lý trang thiết bị y tế nhằm giải quyết các tồn tại, hạn chế liên quan tới lĩnh vực trang thiết bị y tế, như: Tự động gia hạn giấy phép nhập khẩu, số đăng ký lưu hành, khơi thông các vướng mắc trong nhập khẩu trang thiết bị y tế (Giấy phép nhập khẩu TTBYT đã được cấp từ ngày 01/01/2018 đến 31/12/2021 được tiếp tục sử dụng đến hết ngày 31/12/2024; số đăng ký lưu hành đối với TTBYT là sinh phẩm chẩn đoán invitro đã được cấp từ ngày 01/01/2014 đến ngày 31/12/2019 được tiếp tục sử dụng đến hết ngày 31/12/2024), thay đổi quản lý, kê khai giá TTBYT, quy định để giải quyết vướng mắc trong việc xử lý TTBYT khi bị thu hồi số đăng ký lưu hành...
- Nghị quyết số 30/NQ-CP ngày 04/3/2023 về việc tiếp tục thực hiện các giải pháp bảo đảm thuốc, trang thiết bị y tế trong đó có giải quyết các vấn đề liên quan tới thanh toán chi phí khám bệnh, chữa bệnh bảo hiểm y tế đối với các dịch vụ kỹ thuật thực hiện bằng máy do nhà thầu cung cấp sau khi trúng thầu vật tư, hóa chất theo kết quả lựa chọn nhà thầu được cấp có thẩm quyền phê duyệt theo quy định của Luật Đấu thầu; hướng dẫn xác định giá gói thầu; việc sử dụng trang thiết bị y tế đã được cá nhân, tổ chức trong ngoài nước hiến, biếu, tặng cho, đóng góp, viện trợ, tài trợ nhưng chưa hoàn thành sở hữu toàn dân để khám bệnh, chữa bệnh.
Với những văn bản được ban hành nêu trên về cơ bản đã tháo gỡ được những khó khăn, vướng mắc của các cơ sở y tế.
Hiện nay, Bộ Y tế đang tiếp tục phối hợp với Bộ Kế hoạch và Đầu tư, Bộ Tài chính, Bảo hiểm xã hội Việt Nam và các Bộ, ngành, cơ quan liên quan tham mưu quyết liệt cho Chính phủ: Sửa đổi Luật Đấu thầu và Thông tư số 08/2022/TT-BKHĐT ngày 31/5/2022 Quy định chi tiết việc cung cấp, đăng tải thông tin về đấu thầu và lựa chọn nhà thầu trên hệ thống mạng đấu thầu quốc gia; sửa đổi Luật Giá, Nghị định số 29/NĐ-CP, Nghị định số 151/NĐ-CP; Thông tư số 58/2016/TT-BTC; Thông tư số 277/2016/TT-BTC và Thông tư số 278/2016/TT-BTC ngày 14/11/2016... để tạo điều kiện thuận lợi cho công tác mua sắm, đấu thầu thuốc, trang thiết bị y tế; đồng thời hướng dẫn các đơn vị, các cơ sở y tế, địa phương thực hiện các văn bản quy phạm pháp luật liên quan đến mua sắm, đấu thầu thuốc, trang thiết bị y tế theo thẩm quyền.
2.2.2. Về công tác chỉ đạo, điều hành, chuyên môn
Trong các năm qua, Bộ Y tế đã quyết liệt triển khai nhiều giải pháp nhằm tăng cường xử lý, giải quyết việc đăng ký lưu hành, gia hạn đăng ký lưu hành thuốc, nguyên liệu làm thuốc:
- Đẩy nhanh tiến độ cấp, gia hạn giấy đăng ký lưu hành theo quy định tại Luật Dược; ban hành các Thông tư liên quan đến công tác đăng ký thuốc, đẩy mạnh việc cắt giảm, đơn giản hóa thủ tục hành chính, đặc biệt với các quy định về hồ sơ gia hạn giấy đăng ký lưu hành thuốc- Đã rà soát, dự thảo danh mục công bố gần 10.000 thuốc để thực hiện Nghị quyết số 80/2023/QH15 ngày 09/01/2023.
- Chỉ đạo thực hiện việc điều chỉnh giá thuốc kê khai, kê khai lại đối với các mặt hàng do tăng giá khách quan.
- Thực hiện phân cấp toàn diện phê duyệt thẩm quyền quyết định mua sắm, kế hoạch lựa chọn nhà thầu mua thuốc cho các cơ sở y tế trực thuộc Bộ.
- Đẩy nhanh tiến độ thực hiện mua sắm tập thuốc quốc gia, đàm phán giá (Trong năm 2022, đã tổ chức thành công 03 gói thầu thuốc thuộc danh mục đấu thầu tập trung cấp Quốc gia: giảm giá 1.418 tỷ đồng so với giá kế hoạch (giảm gần 18%); đàm phán giá thành công 61/69 mặt hàng thuốc: giảm giá 1.995 tỷ đồng (xấp xỉ 15%)).
- Kịp thời hỗ trợ các cơ sở y tế có vướng mắc liên quan đến thiếu thuốc.
- Tăng cường công tác giáo dục chính trị, tư tưởng và các giải pháp nhằm ổn định tư tưởng cho cán bộ, công chức và chuyên gia thẩm định hồ sơ, tiếp tục đề xuất các giải pháp khắc phục tình trạng công chức và chuyên gia thẩm định xin thôi việc hoặc không tham gia thẩm định hồ sơ; đẩy mạnh ứng dụng công nghệ thông tin trong việc giải quyết hồ sơ trực tuyến, đến nay việc tiếp nhận hồ sơ của hầu hết các thủ tục hành chính trong đăng ký thuốc đã thực hiện trực tuyến; sửa đổi quy định về thu phí đăng ký thuốc và chế độ thù lao cho chuyên gia để thúc đẩy công tác thẩm định hồ sơ ...
- Bộ Y tế đã thành lập 04 Đoàn kiểm tra, khảo sát tình hình cung ứng, sử dụng thuốc, vật tư, trang thiết bị tại các cơ sở y tế, đồng thời có văn bản yêu cầu các Sở Y tế, các bệnh viện Trung ương báo cáo về tình hình cung ứng thuốc, vật tư, trang thiết bị y tế để đánh giá thực trạng cung ứng thuốc, trang thiết bị y tế tại các cơ sở y tế. Phối hợp với các cơ sở y tế, các địa phương đánh giá tình hình thực hiện đảm bảo thuốc, vật tư y tế.
- Phối hợp cùng các địa phương và các cơ sở y tế rà soát các vướng mắc liên quan tới việc đảm bảo thuốc, trang thiết bị vật tư y tế của các cơ sở y tế để tổng hợp đề xuất các cấp có thẩm quyền giải quyết.
2.3. Về việc đảm bảo trang bị đầy đủ trang thiết bị y tế và biên chế y bác sĩ, nhất là đối với tuyến cơ sở
- Việc đầu tư cho y tế cơ sở, y tế dự phòng, trước hết thuộc trách nhiệm của chính quyền địa phương theo các chính sách của Đảng, Nhà nước và hướng dẫn của Bộ Y tế. Ngày 05/12/2016, Thủ tướng Chính phủ đã ban hành Quyết định số 2348/QĐ-TTg phê duyệt Đề án Xây dựng và phát triển mạng lưới y tế cơ sở trong tình hình mới, trong đó đã quy định: Ủy ban nhân dân các tỉnh, thành phố trực thuộc Trung ương bố trí ngân sách địa phương (cả chi đầu tư và chi sự nghiệp) cho Đề án, ưu tiên phân bổ kinh phí từ Chương trình mục tiêu quốc gia xây dựng nông thôn mới, Chương trình mục tiêu quốc gia giảm nghèo bền vững, Chương trình mục tiêu đầu tư phát triển hệ thống y tế địa phương, Chương trình mục tiêu y tế - dân số và huy động các nguồn vốn hợp pháp để thực hiện Đề án. Bảo đảm đủ nhân lực, nguồn tài chính cho hoạt động của y tế cơ sở; xây dựng và ban hành các cơ chế, chính sách, chế độ để thu hút bác sỹ về làm việc tại tuyến y tế cơ sở.
Ngày 30/01/2022, Chính phủ đã ban hành Nghị quyết số 11/NQ-CP về Chương trình phục hồi và phát triển kinh tế - xã hội và triển khai Nghị quyết số 43/2022/QH15 của Quốc hội về chính sách tài khóa, tiền tệ hỗ trợ Chương trình. Chương trình phân bổ gần 14.000 tỷ đồng cho ngành Y tế để đầu tư xây mới, cải tạo, nâng cấp, hiện đại hóa hệ thống y tế cơ sở, y tế dự phòng, Trung tâm Kiểm soát bệnh tật tỉnh, nâng cao năng lực phòng, chống dịch bệnh của các Viện nghiên cứu, Bệnh viện cấp trung ương gắn với đào tạo, nâng cao chất lượng nguồn nhân lực trong lĩnh vực y tế, sản xuất vắc xin trong nước, thuốc điều trị COVID-19. Bộ Y tế đã tổng hợp nhu cầu của các địa phương, đơn vị và báo cáo cấp có thẩm quyền để thực hiện việc phân bổ nguồn lực của Chương trình.
Bộ Y tế cũng đã đề xuất Chính phủ triển khai các nguồn lực đầu tư từ Chương trình Mục tiêu Quốc gia xây dựng nông thôn mới, giảm nghèo bền vững, phát triển kinh tế - xã hội vùng đồng bào dân tộc thiểu số và miền núi giai đoạn 2021-2025; tiếp tục triển khai các dự án đầu tư cho y tế cơ sở từ nguồn vốn vay của Ngân hàng thế giới (WB) và Ngân hàng Phát triển Châu Á (ADB).
- Thực hiện Nghị định số 106/2020/NĐ-CP ngày 10/9/2020 của Chính phủ quy định về vị trí việc làm, số người làm việc trong đơn vị sự nghiệp công lập, ngày 17/02/2023, Bộ Y tế đã ban hành Thông tư số 03/2023/TT-BYT hướng dẫn vị trí việc làm, định mức số lượng người làm việc, cơ cấu viên chức theo chức danh nghề nghiệp trong đơn vị sự nghiệp y tế công lập. Theo đó, Thông tư hướng dẫn về vị trí việc làm, định mức số lượng người làm việc, cơ cấu viên chức theo chức danh nghề nghiệp tối thiểu trong đơn vị sự nghiệp y tế công lập thuộc các Bộ, ngành và địa phương. Các đơn vị sự nghiệp y tế công lập có số lượng người làm việc chưa đáp ứng đủ định mức tối thiểu quy định tại Thông tư này phải có phương án tuyển dụng, bố trí, sắp xếp viên chức để bảo đảm định mức này, hoàn thành trước 31/12/2025. Do vậy, theo thẩm quyền Ủy ban nhân dân cấp tỉnh, cấp huyện, căn cứ quy định và tình hình thực tế của địa phương để quyết định việc bố trí số lượng người làm việc cho các trạm y tế xã, phường trên địa bàn cho phù hợp.
Trên đây là nội dung trả lời đối với kiến nghị của cử tri tỉnh Tây Ninh liên quan đến lĩnh vực y tế, Bộ Y tế trân trọng kính gửi Đoàn Đại biểu Quốc hội để thông tin đến cử tri.
Xin trân trọng cảm ơn./.
Nơi nhận: - Như trên; - Ban Dân nguyện - UBTVQH; - VPCP: QHĐP, TH; - VPQH; - Các đ/c Thứ trưởng BYT; - BYT: BH, KH-TC, TB-CT, QLD; - Cổng TTĐT Bộ Y tế (để đăng tải); - Lưu: VT, VPB1.
BỘ TRƯỞNG Đào Hồng Lan
lồng nhau (bên trong) hay không
const memberID = 0;
const vbID = 'bb1f7e775419b8671cb122a15f911388';
const unlockAllPhanTich = true;
// State management cho phân tích
let isAnalyzing = false; // Có đang phân tích không
let currentAnalyzingAddress = null; // Address đang được phân tích
let currentAnalyzingElement = null; // Element đang được phân tích
let currentAnalyzingBadge = null; // Badge của element đang phân tích
let isPanelOpen = false; // Panel phân tích có đang mở không
// Typing effect state
let typingTimerId = null;
let typingCancelled = false;
// Thinking GIF state
let thinkingGifIntervalId = null;
let thinkingGifActive = false;
let thinkingGifCurrent = 0; // chỉ số GIF hiện tại 1..10
function isInViewportAndTabNoiDung(element) {
const rect = element.getBoundingClientRect();
const buffer = 1500; // Buffer to preload content below the viewport (approx. 50+ lines)
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
const isInViewport = rect.top < viewHeight + buffer && rect.bottom >= 0;
const isInTabNoiDung = $(element).closest('#tab_noi_dung_vb').length > 0;
return isInViewport && isInTabNoiDung;
}
function getAddress(element) {
const validTags = ['trichyeu', 'cancu', 'phan', 'chuong', 'muc', 'tieumuc', 'dieu', 'khoan', 'diem'];
const $parent = $(element).closest(validTags.join(','));
if (!$parent.length) {
return null;
}
let addr = $parent.attr('address');
if (!addr && $parent.prop('tagName').toLowerCase() === 'trichyeu') {
addr = 'trichyeu';
$parent.attr('address', addr);
}
return addr || null;
}
function processTnplClasses($element) {
const tnplKeysInLine = new Set(); // key = slug hoặc text (thường là slug)
$element.find('tnpl').each(function () {
const $tnpl = $(this);
const tnplSlug = ($tnpl.attr('slug') || '').trim().toLowerCase();
const tnplKey = tnplSlug || $tnpl.text().trim().toLowerCase();
// Đã xử lý trong cùng dòng => bỏ
if (tnplKeysInLine.has(tnplKey)) {
return;
}
tnplKeysInLine.add(tnplKey);
let tnplExists = false;
// Chỉ duyệt các tnpl đã được tô màu (class on)
$('tnpl.on').each(function () {
const $existingTnpl = $(this);
const existingSlug = ($existingTnpl.attr('slug') || '').trim().toLowerCase();
const existingKey = existingSlug || $existingTnpl.text().trim().toLowerCase();
if (
existingKey === tnplKey &&
isInViewportAndTabNoiDung($existingTnpl[0])
) {
tnplExists = true;
return false; // break each
}
});
if (!tnplExists) {
$tnpl.addClass('on');
}
});
}
function processQueue() {
while (pendingRequests < maxConcurrentRequests && requestQueue.length > 0) {
const task = requestQueue.shift();
pendingRequests++;
task()
.always(() => {
pendingRequests--;
processQueue();
});
}
}
function processVisibleParagraphs() {
try {
$('#tab_noi_dung_vb p:not([is-posted="1"])').each(function () {
let $element = $(this);
if (isInViewportAndTabNoiDung(this)) {
$element.attr('is-posted', '1');
$element.addClass('loading-content');
let p_innerHTML = $element.html();
let address = null;
if (cac_cau_hinh.loai_noi_dung.includes('docs')) {
address = getAddress($element);
}
const isSubP = $element.parents('p').length > 0;
if (isSubP && !allow_sub_p) {
$element.removeClass('loading-content');
return; // Không gửi nếu không cho phép
}
const postData = { p_content: p_innerHTML, cac_cau_hinh, address, vb_ngaybanhanh: '2023-03-15 00:00:00 AM' };
if (isSubP && allow_sub_p) {
postData.sub_p = 1;
}
requestQueue.push(() =>
$.ajax({
url: '//tnpl' + (Math.floor(Math.random() * 10) + 1) + '.hethongphapluat.com/tien-ich/tim.tien.ich.php',
type: 'POST',
data: postData,
success: function(response) {
$element.html(response);
processTnplClasses($element);
// Đợi CTTD và các tiện ích load xong rồi mới attach badge
if (((unlockAllPhanTich) || memberID === 4 || memberID === 3 || memberID === 2) && typeof attachPhanTichBadge === 'function') {
setTimeout(function() {
// $element chính là thẻ p, kiểm tra và attach badge trực tiếp
const $parent = $element.closest('phan, chuong, muc, tieumuc, dieu, khoan, diem');
if ($parent.length > 0 && $parent.find('.badge-phan-tich[data-for="' + $parent.attr('address') + '"]').length === 0) {
const address = $parent.attr('address');
$element.attr('data-address', address);
// Lấy tên loại thẻ cho tooltip
const parentType = getParentTypeName($parent.prop('tagName').toLowerCase());
// Append badge VÀO PARENT (dieu, khoan,...) thay vì vào để tránh xung đột CTTD
const extraClass = (unlockAllPhanTich && memberID <= 0) ? ' upgrade-require' : '';
const $badge = $('');
$parent.append($badge);
// Thêm class để CSS set position: relative CHỈ cho element có badge
$parent.addClass('has-phan-tich-badge');
}
// Xử lý các p con (nếu có sub-p)
attachPhanTichBadge($element);
}, 3); // Đợi 300ms để CTTD render xong
}
},
complete: function() {
$element.removeClass('loading-content');
}
})
);
processQueue();
}
});
} catch(e) {
}
}
$(window).on('scroll resize', function () {
processVisibleParagraphs();
});
processVisibleParagraphs();
// Chức năng phân tích điều luật (mở theo lịch unlockAllPhanTich cho tất cả, nhưng khách click sẽ mở modal đăng nhập/mua gói)
if ((unlockAllPhanTich) || memberID === 4 || memberID === 3 || memberID === 2) {
// Modal cảnh báo
function showWarningModal(message) {
// Tạo modal nếu chưa có
if ($('#warningModal').length === 0) {
const modalHTML = `
`;
$('body').append(modalHTML);
}
$('#warningModalBody').html('' + message + '
');
$('#warningModal').modal('show');
}
// Hàm lấy tên tiếng Việt của thẻ
function getParentTypeName(tagName) {
const typeNames = {
'phan': 'Phần',
'chuong': 'Chương',
'muc': 'Mục',
'tieumuc': 'Tiểu mục',
'dieu': 'Điều',
'khoan': 'Khoản',
'diem': 'Điểm'
};
return typeNames[tagName] || 'Nội dung';
}
// Chuyển Telex -> Unicode cho giá trị (ví dụ: dd->đ, oo->ô, ow->ơ, aa->â, ee->ê, aw->ă, uw->ư)
function telexToUnicode(str) {
if (!str) return str;
// Giữ nguyên số
if (/^\d+$/.test(str)) return str;
let s = String(str);
// dd / ĐĐ
s = s.replace(/dd/g, 'đ');
s = s.replace(/DD/g, 'Đ');
// nguyên âm có mũ/dấu
s = s.replace(/aa/g, 'â').replace(/AA/g, 'Â');
s = s.replace(/ee/g, 'ê').replace(/EE/g, 'Ê');
s = s.replace(/oo/g, 'ô').replace(/OO/g, 'Ô');
s = s.replace(/ow/g, 'ơ').replace(/OW/g, 'Ơ');
s = s.replace(/uw/g, 'ư').replace(/UW/g, 'Ư');
s = s.replace(/aw/g, 'ă').replace(/AW/g, 'Ă');
return s;
}
function attachPhanTichBadge($container) {
const validTags = 'phan, chuong, muc, tieumuc, dieu, khoan, diem';
$container.find('p').each(function() {
const $p = $(this);
const $parent = $p.closest(validTags);
if ($parent.length > 0) {
const address = $parent.attr('address');
// Kiểm tra đã có badge cho parent này chưa
if ($parent.find('.badge-phan-tich[data-for="' + address + '"]').length === 0) {
// Lưu address vào data attribute
$p.attr('data-address', address);
// Lấy tên loại thẻ cho tooltip
const parentType = getParentTypeName($parent.prop('tagName').toLowerCase());
// Append badge vào PARENT, không vào
const extraClass = (unlockAllPhanTich && memberID <= 0) ? ' upgrade-require' : '';
const $badge = $('');
$parent.append($badge);
// Thêm class để CSS set position: relative CHỈ cho element có badge
$parent.addClass('has-phan-tich-badge');
}
}
});
}
// Helper: Escape HTML entities
function escapeHtml(text) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return String(text).replace(/[&<>"']/g, function(m) { return map[m]; });
}
// Helper: Convert Markdown to HTML (đơn giản)
function markdownToHtml(markdown) {
if (!markdown) return '';
let html = markdown;
// Headers
html = html.replace(/^### (.*$)/gim, '
$1 ');
html = html.replace(/^## (.*$)/gim, '$1 ');
html = html.replace(/^# (.*$)/gim, '$1 ');
// Bold
html = html.replace(/\*\*(.*?)\*\*/g, '$1 ');
// Italic
html = html.replace(/\*(.*?)\*/g, '$1 ');
// Blockquote
html = html.replace(/^> (.*$)/gim, '$1 ');
html = html.replace(/^> (.*$)/gim, '$1 ');
// Lists (unordered)
html = html.replace(/^\- (.*$)/gim, '$1 ');
html = html.replace(/(.*<\/li>)/s, '');
// Lists (ordered)
html = html.replace(/^\d+\. (.*$)/gim, ' $1 ');
// Line breaks và paragraphs
html = html.split('\n\n').map(para => {
para = para.trim();
if (para.startsWith('')) {
return para;
}
if (para) {
return '' + para.replace(/\n/g, ' ') + '
';
}
return '';
}).join('\n');
// Clean up multiple line breaks
html = html.replace(/\n{3,}/g, '\n\n');
return html;
}
// Panel fixed position
function closePhanTichPanel() {
const $panel = $('#phanTichPanel');
if ($panel.length) {
$panel.removeClass('show');
setTimeout(() => {
$panel.remove();
}, 300);
}
// Stop typing animation nếu đang chạy
stopThinkingTyping();
// Reset highlight và badge khi đóng panel
if (currentAnalyzingElement) {
currentAnalyzingElement.removeClass('highlight-border-persistent');
}
if (currentAnalyzingBadge) {
currentAnalyzingBadge.text('Phân tích').removeClass('analyzing');
currentAnalyzingBadge.data('analyzing', false);
currentAnalyzingBadge.data('hovering', false);
currentAnalyzingBadge.css({display: 'none'}); // Ẩn badge khi đóng
}
// Reset tất cả các element khác (trong trường hợp có nhiều)
$('#tab_noi_dung_vb .highlight-border-persistent').removeClass('highlight-border-persistent');
$('#tab_noi_dung_vb .badge-phan-tich-container.analyzing').each(function() {
$(this).text('Phân tích').removeClass('analyzing').data('analyzing', false);
});
// Check: có CTTD pointer đang mở không?
const $visiblePointers = $('.pointer:visible');
const hadCTTDOpen = $visiblePointers.length > 0;
if (hadCTTDOpen) {
// CÓ CTTD đang mở → giữ rightdocinfo ẩn
} else {
// KHÔNG có CTTD → SHOW lại rightdocinfo
const $rightdocinfo = $('#rightdocinfo');
if ($rightdocinfo.length > 0) {
$rightdocinfo.show();
}
}
// Reset state
isAnalyzing = false;
currentAnalyzingAddress = null;
currentAnalyzingElement = null;
currentAnalyzingBadge = null;
isPanelOpen = false; // Đánh dấu panel đã đóng
}
// Panel đã song song với rightdocinfo → không cần MutationObserver nữa
// Resize event để update panel dimensions khi browser resize
let resizeTimer;
$(window).on('resize', function() {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function() {
if (isPanelOpen && $('#phanTichPanel').length > 0) {
updatePanelDimensions();
if ($('#phanTichPanelBody').hasClass('thinking-mode')) {
updateThinkingGifHeight();
}
}
}, 250); // Debounce 250ms
});
// Function để detect và áp dụng dimensions từ rightdocinfo
function updatePanelDimensions() {
const $panel = $('#phanTichPanel');
const $rightdocinfo = $('#rightdocinfo');
const $docRightCol = $('#doc-right-col');
// Mobile: dùng bottom sheet → để CSS điều khiển, bỏ qua reposition bằng JS
if ($(window).width() <= 768) {
return;
}
if ($panel.length === 0) return;
// Ưu tiên: doc-right-col > rightdocinfo
let $reference = $docRightCol.length > 0 ? $docRightCol : $rightdocinfo;
// Nếu reference bị ẩn (display:none), tạm show để get dimensions
let wasHidden = false;
if ($reference.length > 0 && !$reference.is(':visible')) {
wasHidden = true;
$reference.css('visibility', 'hidden').show();
}
if ($reference.length > 0) {
const refWidth = $reference.outerWidth();
const refOffset = $reference.offset();
if (refWidth && refOffset) {
// Tính vị trí right từ edge màn hình
const windowWidth = $(window).width();
const rightPosition = windowWidth - (refOffset.left + refWidth);
$panel.css({
'width': refWidth + 'px',
'right': rightPosition + 'px'
});
} else {
}
// Restore trạng thái hidden nếu cần
if (wasHidden) {
$reference.hide().css('visibility', '');
}
}
}
// Hiệu ứng typing giả lập đang phân tích trong panel
function stopThinkingTyping() {
typingCancelled = true;
if (typingTimerId) {
clearTimeout(typingTimerId);
typingTimerId = null;
}
// Dừng trình chiếu ảnh khi dừng typing
stopThinkingImages();
}
// Helper GIF: chọn chỉ số ảnh mới 1..10 khác với exclude
function randomGifIndex(exclude) {
let n = exclude;
while (n === exclude) {
n = Math.floor(Math.random() * 10) + 1;
}
return n;
}
// Helper GIF: preload rồi gán src cho img, gọi callback sau khi load xong (hoặc lỗi)
function setGifSrc($img, idx, cb) {
const url = '/assets/images/gif/researching-' + idx + '.gif';
const updateWrapHeight = function(nW, nH){
try {
const $wrap = $img.closest('#thinkingGifWrapper');
if ($wrap.length && nW && nH) {
const wrapW = $wrap.width();
const maxW = wrapW * 0.9; // khớp với CSS max-width:90%
const displayW = Math.min(nW, maxW);
const displayH = nH * (displayW / nW);
$wrap.css('height', displayH + 'px');
}
} catch(e) { /* ignore */ }
};
if ($img.attr('src') === url) {
// Ảnh trùng src -> vẫn cập nhật lại chiều cao wrapper theo kích thước hiển thị hiện tại
const el = $img[0];
if (el && el.naturalWidth && el.naturalHeight) {
updateWrapHeight(el.naturalWidth, el.naturalHeight);
}
if (cb) cb();
return;
}
const pre = new Image();
pre.onload = function() {
$img.attr('src', url);
updateWrapHeight(pre.naturalWidth, pre.naturalHeight);
if (cb) cb();
};
pre.onerror = function() {
$img.attr('src', url);
// Không lấy được kích thước tự nhiên -> để auto
const $wrap = $img.closest('#thinkingGifWrapper');
if ($wrap.length) { $wrap.css('height', 'auto'); }
if (cb) cb();
};
pre.src = url;
}
function updateThinkingGifHeight() {
const $wrap = $('#thinkingGifWrapper');
if ($wrap.length === 0) return;
const $show = $('#thinkingGifA.visible, #thinkingGifB.visible').first();
if ($show.length === 0) return;
const el = $show[0];
if (!el.naturalWidth || !el.naturalHeight) return;
const wrapW = $wrap.width();
const maxW = wrapW * 0.9;
const displayW = Math.min(el.naturalWidth, maxW);
const displayH = el.naturalHeight * (displayW / el.naturalWidth);
$wrap.css('height', displayH + 'px');
}
function startThinkingImages() {
// Nếu body/khung chưa sẵn sàng thì bỏ qua
const $wrap = $('#thinkingGifWrapper');
if ($wrap.length === 0) return;
// Clear trước nếu đang chạy
stopThinkingImages();
thinkingGifActive = true;
const $a = $('#thinkingGifA');
const $b = $('#thinkingGifB');
$a.removeClass('visible');
$b.removeClass('visible');
// Ảnh đầu tiên
thinkingGifCurrent = randomGifIndex(0);
let useA = true; // ảnh A hiển thị trước
setGifSrc($a, thinkingGifCurrent, function(){ $a.addClass('visible'); });
// Mỗi 3s đổi ảnh, crossfade 0.5s qua CSS
thinkingGifIntervalId = setInterval(function(){
if (!thinkingGifActive) return;
const nextIdx = randomGifIndex(thinkingGifCurrent);
const $show = useA ? $b : $a; // show ảnh còn lại
const $hide = useA ? $a : $b;
setGifSrc($show, nextIdx, function(){
// Bắt đầu chuyển ảnh: ẩn ảnh cũ, hiện ảnh mới
$hide.removeClass('visible');
setTimeout(function(){ $show.addClass('visible'); }, 10);
thinkingGifCurrent = nextIdx;
useA = !useA;
});
}, 5000);
}
function stopThinkingImages() {
thinkingGifActive = false;
if (thinkingGifIntervalId) {
clearInterval(thinkingGifIntervalId);
thinkingGifIntervalId = null;
}
}
// Giải quyết address: nếu không có '_' thì decrypt (ưu tiên API, fallback client), ngược lại trả về nguyên vẹn
function clientDecrypt(encrypted, key) {
try {
const bin = atob(encrypted);
let out = '';
for (let i = 0; i < bin.length; i++) {
const ch = bin.charCodeAt(i);
const k = key.charCodeAt(i % key.length);
out += String.fromCharCode(ch ^ k);
}
// Chuẩn hóa tương tự server
out = out.toLowerCase().replace(/[^a-z0-9_]/g, '');
return out || encrypted;
} catch (e) {
return encrypted;
}
}
function resolveAddress(address) {
return new Promise(function(resolve) {
if (!address) { resolve(''); return; }
const addr = String(address);
const lower = addr.toLowerCase();
if (lower === 'trichyeu' || lower === 'cancu' || addr.indexOf('_') !== -1) {
resolve(addr);
return;
}
const randomServer = Math.floor(Math.random() * 10) + 1;
$.ajax({
url: '//tnpl' + randomServer + '.hethongphapluat.com/tien-ich/ajax/decrypt.ndsh.address.php',
type: 'POST',
data: { address_encrypted: addr },
timeout: 10000,
success: function(resp) {
try {
// jQuery sẽ parse JSON theo header, nhưng vẫn fallback nếu là string
if (typeof resp === 'string') { resp = JSON.parse(resp); }
} catch(e) { /* ignore */ }
if (resp && resp.ok && resp.address) {
resolve(resp.address);
} else {
// Fallback client decrypt
resolve(clientDecrypt(addr, 'htpl_noi_dung_vb_address'));
}
},
error: function() {
// Fallback client decrypt
resolve(clientDecrypt(addr, 'htpl_noi_dung_vb_address'));
}
});
});
}
function startThinkingTyping(address) {
// Reset trước khi bắt đầu
stopThinkingTyping();
typingCancelled = false;
const $body = $('#phanTichPanelBody');
if ($body.length === 0) return;
// Đánh dấu chế độ thinking để căn giữa toàn bộ nội dung trong body
$body.addClass('thinking-mode');
// Khởi tạo container nếu chưa có
if ($('#thinkingContainer').length === 0) {
$body.html('\
\
\
\
');
}
$('#thinkingText').html('');
// Khởi động slideshow ảnh thinking
startThinkingImages();
// Chờ resolve address (decrypt nếu cần) rồi mới bắt đầu typing
resolveAddress(address).then(function(addrPlain) {
if (typingCancelled) return;
const displayNameLarge = getElementDisplayNameLargeFirst(addrPlain);
$('.processing-text').text('Đang xử lý phân tích ' + displayNameLarge.toLowerCase() + '...');
// Câu nói đa dạng cho từng bước
const variants = [
[
'Tôi đã nhận được yêu cầu phân tích {name}...',
'Cảm ơn bạn đã gửi yêu cầu phân tích {name}, tôi sẽ bắt đầu...',
'Bạn đã yêu cầu tôi phân tích {name}, hãy chờ tôi lập kế hoạch...',
'Yêu cầu phân tích {name} đã được ghi nhận, tôi đang chuẩn bị...'
],
[
'Tiếp theo, tôi sẽ đọc kỹ nội dung chi tiết của {name}...',
'Bây giờ tôi cần xem xét kỹ nội dung của {name}...',
'Đang mở và duyệt qua nội dung {name}...'
],
[
'Tôi đã đọc xong. Tôi sẽ kiểm tra xem {name} có bị sửa đổi, bổ sung, thay thế hoặc bãi bỏ bởi điều khoản nào không...',
'Tôi sẽ đối chiếu các văn bản để xem {name} có thay đổi hiệu lực nào không...',
'Tiếp tục kiểm tra trạng thái hiệu lực và các lần sửa đổi của {name}...'
],
[
'Tôi cũng cần xem {name} có được hướng dẫn bởi điều luật nào không...',
'Đang tìm các quy định hướng dẫn áp dụng liên quan đến {name}...',
'Kiểm tra các văn bản hướng dẫn có nhắc đến {name}...'
],
[
'Tôi sẽ kiểm tra {name} có viện dẫn/nhắc đến điều luật khác để tham chiếu hay không...',
'Đang rà soát các điều khoản được {name} đề cập đến...',
'Tìm các tham chiếu pháp lý xuất hiện trong {name}...'
],
[
'Tôi sẽ nghiên cứu về phạm vi điều chỉnh và đối tượng áp dụng'
],
[
'Bây giờ tôi cần tìm ví dụ minh họa cho nội dung điều này...'
],
[
'Tôi cũng cần bổ sung vài lưu ý thực tiễn trong bài phân tích của tôi...'
],
[
'Giờ tôi sẽ viết phần kết luận của bài phân tích...'
],
[
'Bây giờ tôi bắt đầu phân tích chi tiết {name}...',
'Bắt đầu tổng hợp và phân tích {name}...',
'Tiến hành phân tích nội dung {name}...'
]
];
const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
const lines = variants.map(group => pick(group).replace(/\{name\}/g, displayNameLarge));
let lineIndex = 0;
let charIndex = 0;
const speedMin = 12; // ms
const speedMax = 25; // ms
const linePause = 2000; // ms chờ 2s giữa các câu
function typeNextChar() {
if (typingCancelled) return;
const line = lines[lineIndex];
if (charIndex < line.length) {
$('#thinkingText').append(line.charAt(charIndex));
charIndex++;
const delay = Math.floor(Math.random() * (speedMax - speedMin + 1)) + speedMin;
typingTimerId = setTimeout(typeNextChar, delay);
} else {
// Hoàn tất 1 câu
if (lineIndex < lines.length - 1) {
// Chờ 2s rồi chuyển sang câu tiếp theo, thay thế câu cũ (không append)
typingTimerId = setTimeout(function() {
if (typingCancelled) return;
$('#thinkingText').html('');
lineIndex++;
charIndex = 0;
typeNextChar();
}, linePause);
} else {
// Câu cuối cùng -> giữ nguyên, chỉ để caret nhấp nháy; không loop
return;
}
}
}
typeNextChar();
});
}
function openPhanTichPanel(address, vbID) {
// Kiểm tra nếu đang phân tích element khác
if (isAnalyzing && currentAnalyzingAddress && currentAnalyzingAddress !== address) {
// Giải mã địa chỉ hiện đang phân tích trước khi hiển thị trong modal
resolveAddress(currentAnalyzingAddress).then(function(addrPlain) {
const currentName = getElementDisplayNameLargeFirst(addrPlain);
showWarningModal('Vui lòng chờ phân tích ' + currentName + ' hoàn tất...');
});
return;
}
// Nếu đang phân tích cùng element → không làm gì
if (isAnalyzing && currentAnalyzingAddress === address) {
return;
}
// Panel sẽ fixed position append vào body
const $rightdocinfo = $('#rightdocinfo');
// KHÔNG ẨN CTTD pointer - cho phép CTTD và panel cùng tồn tại
// ẨN rightdocinfo để tiết kiệm không gian
if ($rightdocinfo.length > 0) {
$rightdocinfo.hide();
}
// XÓA highlight persistent của TẤT CẢ elements cũ trước
$('#tab_noi_dung_vb .highlight-border-persistent').removeClass('highlight-border-persistent');
// Tìm element đang được phân tích và badge của nó
const $element = $('[address="' + address + '"]');
const $badge = $element.find('.badge-phan-tich-container[data-for="' + address + '"]').first();
// Set state
isAnalyzing = true;
currentAnalyzingAddress = address;
currentAnalyzingElement = $element;
currentAnalyzingBadge = $badge;
// Thêm highlight persistent cho element MỚI này
$element.addClass('highlight-border-persistent');
// Thay đổi badge thành "Đang phân tích..." và giữ hiển thị
if ($badge.length > 0) {
$badge.text('Đang phân tích...').addClass('analyzing');
// Giữ badge hiển thị và ở đúng vị trí
$badge.data('analyzing', true);
$badge.data('hovering', true); // Prevent auto-hide
// Đảm bảo badge hiển thị ở đúng vị trí (vì dùng position: fixed)
showPhanTichBadgeForParent($element);
}
// Tạo panel nếu chưa có - fixed position append vào body
if ($('#phanTichPanel').length === 0) {
const debugHTML = (memberID === 3 || memberID === 4) ? `
Debug Mode
` : '';
const panelHTML = `
`;
// Append vào body (fixed position không cần container cụ thể)
$('body').append(panelHTML);
// Detect width từ rightdocinfo và áp dụng cho panel
updatePanelDimensions();
// Trigger show và set flag
setTimeout(() => {
$('#phanTichPanel').addClass('show');
isPanelOpen = true;
// Bắt đầu typing
stopThinkingTyping();
startThinkingTyping(address);
}, 10);
} else {
// Khởi tạo giao diện typing khi mở lại panel
$('#phanTichPanelBody').addClass('thinking-mode').html('');
// Update dimensions khi re-open
updatePanelDimensions();
$('#phanTichPanel').addClass('show');
isPanelOpen = true;
// Bắt đầu typing
stopThinkingTyping();
startThinkingTyping(address);
}
// Bind nút đóng và ESC
$(document).off('click.closePhanTich').on('click.closePhanTich', '.close-phan-tich', function() {
closePhanTichPanel();
});
$(document).off('keyup.closePhanTich').on('keyup.closePhanTich', function(e) {
if (e.key === 'Escape') closePhanTichPanel();
});
// Bind nút refresh - phân tích lại
$(document).off('click.refreshPhanTich').on('click.refreshPhanTich', '.btn-refresh-phan-tich', function(e) {
e.preventDefault();
e.stopPropagation();
const $btn = $(this);
const $icon = $btn.find('i');
// Disable button và thêm animation
$btn.prop('disabled', true);
$icon.addClass('fa-spin');
// Show typing trong panel thay cho loading
$('#phanTichPanelBody').addClass('thinking-mode').html('');
stopThinkingTyping();
startThinkingTyping(address);
// Gọi API xóa cache trước
deletePhanTichCache(address, vbID, function(deleteSuccess) {
if (deleteSuccess) {
// Sau khi xóa cache, gọi lại API phân tích
callPhanTichAPI(address, vbID, function() {
// Enable lại button
$btn.prop('disabled', false);
$icon.removeClass('fa-spin');
});
} else {
$('#phanTichPanelBody').html(`
Lỗi! Không thể xóa cache. Vui lòng thử lại.
`);
$btn.prop('disabled', false);
$icon.removeClass('fa-spin');
}
});
});
// Gọi API phân tích (dùng function helper)
callPhanTichAPI(address, vbID);
}
// Helper: Gọi API phân tích (tách riêng để dùng lại)
function callPhanTichAPI(address, vbID, callback, attempt) {
attempt = attempt || 1;
const randomServer = Math.floor(Math.random() * 10) + 1;
const debugMode = $('#debugModePhanTich').is(':checked') ? 1 : 0;
$.ajax({
url: '//tnpl' + randomServer + '.hethongphapluat.com/tien-ich/phan.tich.dieu.luat.php',
type: 'POST',
contentType: 'application/json',
timeout: 300000, // 5 phút
data: JSON.stringify({
address: address,
vb_id: vbID,
debug: debugMode
}),
success: function(response) {
if (response && response.ok) {
// Thành công -> kết thúc thinking và reset trạng thái
stopThinkingTyping();
if (currentAnalyzingBadge) {
currentAnalyzingBadge.text('Phân tích').removeClass('analyzing');
currentAnalyzingBadge.data('analyzing', false);
}
isAnalyzing = false;
// Render kết quả phân tích với hiệu ứng xuất hiện dần từ trên xuống dưới
let html = '';
html += '';
html += '';
html += '
' + markdownToHtml(response.phan_tich) + '
';
// Khuyến cáo thay cho thống kê token
html += '
';
html += 'Những thông tin em vừa cung cấp chỉ mang tính chất tham khảo, không đại diện cho tư vấn chính thức của luật sư. Quý khách nên tìm đến sự tư vấn trực tiếp từ Luật sư hoặc đơn vị pháp lý có chuyên môn để được hỗ trợ cụ thể cho trường hợp của mình.';
html += '
';
html += '
';
$('#phanTichPanelBody').removeClass('thinking-mode').html(html);
applyFadeReveal();
} else {
// Không ok -> nếu là quá tải và chưa vượt số lần thử thì retry
const msg = response && response.error ? response.error : '';
if (isOverloadedMessage(msg) && attempt < 50 && isPanelOpen && isAnalyzing && currentAnalyzingAddress === address) {
const delay = Math.min(1200 + attempt * 100, 5000);
setTimeout(function() { callPhanTichAPI(address, vbID, callback, attempt + 1); }, delay);
return;
}
// Hết số lần thử hoặc không phải quá tải -> hiển thị lỗi
stopThinkingTyping();
if (currentAnalyzingBadge) {
currentAnalyzingBadge.text('Phân tích').removeClass('analyzing');
currentAnalyzingBadge.data('analyzing', false);
}
isAnalyzing = false;
if (isOverloadedMessage(msg)) {
$('#phanTichPanelBody').removeClass('thinking-mode').html(`
Hiện tại A.I đang bị quá tải , vui lòng thử lại sau ít phút!
Thử lại
`);
$(document).off('click.tryAgainPanel').on('click.tryAgainPanel', '#btnTryAgainPanel', function() {
openPhanTichPanel(address, vbID);
});
} else {
$('#phanTichPanelBody').removeClass('thinking-mode').html(`
Lỗi! ${escapeHtml(msg || 'Không thể phân tích điều luật.')}
Vui lòng thử lại sau.
`);
}
}
if (callback) callback();
},
error: function(xhr, status, error) {
// Nếu quá tải và chưa quá 50 lần -> retry, giữ hiệu ứng thinking và trạng thái analyzing
let errorMsg = error;
if (xhr.responseJSON && xhr.responseJSON.error) {
errorMsg = (xhr.responseJSON.error.message || xhr.responseJSON.error) || errorMsg;
} else if (xhr.responseText) {
errorMsg = xhr.responseText;
}
if ((xhr.status === 503 || isOverloadedMessage(errorMsg)) && attempt < 50 && isPanelOpen && isAnalyzing && currentAnalyzingAddress === address) {
const delay = Math.min(1200 + attempt * 100, 5000);
setTimeout(function() { callPhanTichAPI(address, vbID, callback, attempt + 1); }, delay);
return;
}
// Hết số lần thử hoặc lỗi khác -> hiển thị thông báo phù hợp
stopThinkingTyping();
if (currentAnalyzingBadge) {
currentAnalyzingBadge.text('Phân tích').removeClass('analyzing');
currentAnalyzingBadge.data('analyzing', false);
}
isAnalyzing = false;
if (xhr.status === 503 || isOverloadedMessage(errorMsg)) {
$('#phanTichPanelBody').removeClass('thinking-mode').html(`
Hiện tại A.I đang bị quá tải , vui lòng thử lại sau ít phút!
Thử lại
`);
$(document).off('click.tryAgainPanel').on('click.tryAgainPanel', '#btnTryAgainPanel', function() {
openPhanTichPanel(address, vbID);
});
} else {
$('#phanTichPanelBody').removeClass('thinking-mode').html(`
Lỗi! Không thể kết nối đến server phân tích.
Chi tiết: ${escapeHtml(errorMsg)}
`);
}
if (callback) callback();
}
});
}
// Helper: Xóa cache phân tích
function deletePhanTichCache(address, vbID, callback) {
const randomServer = Math.floor(Math.random() * 10) + 1;
$.ajax({
url: '//tnpl' + randomServer + '.hethongphapluat.com/tien-ich/delete.phan.tich.cache.php',
type: 'POST',
contentType: 'application/json',
timeout: 10000,
data: JSON.stringify({
address: address,
vb_id: vbID
}),
success: function(response) {
if (callback) callback(response.ok || false);
},
error: function(xhr, status, error) {
if (callback) callback(false);
}
});
}
// Helper: Lấy tên hiển thị của element từ address (có chuyển Telex -> Unicode ở phần giá trị)
function getElementDisplayName(address) {
if (!address) return 'nội dung';
const addrStr = String(address).toLowerCase();
// Các trường hợp đặc biệt không có cặp key_value
if (addrStr === 'trichyeu') return 'Trích yếu';
if (addrStr === 'cancu') return 'Căn cứ';
// Parse địa chỉ linh hoạt: hỗ trợ cả dạng thiếu cặp
const parts = addrStr.split('_');
const types = new Set(['phan', 'chuong', 'muc', 'tieumuc', 'dieu', 'khoan', 'diem']);
const displayParts = [];
for (let i = 0; i < parts.length; i++) {
const key = parts[i];
if (types.has(key)) {
const label = getParentTypeName(key);
const val = (i + 1 < parts.length) ? parts[i + 1] : '';
const valVN = telexToUnicode(val);
displayParts.push(label + (valVN ? ' ' + valVN : ''));
if (val) i++; // bỏ qua value nếu đã dùng
}
}
const title = displayParts.reverse().join(' ');
if (title) return title;
// Fallback: nếu không parse được, trả về address gốc
return address;
}
// Helper: Lấy tên hiển thị theo thứ tự lớn -> nhỏ (Điều > Khoản > Điểm), có chuyển Telex
function getElementDisplayNameLargeFirst(address) {
if (!address) return 'nội dung';
const addrStr = String(address).toLowerCase();
if (addrStr === 'trichyeu') return 'Trích yếu';
if (addrStr === 'cancu') return 'Căn cứ';
const parts = addrStr.split('_');
const types = new Set(['phan', 'chuong', 'muc', 'tieumuc', 'dieu', 'khoan', 'diem']);
const displayParts = [];
for (let i = 0; i < parts.length; i++) {
const key = parts[i];
if (types.has(key)) {
const label = getParentTypeName(key);
const val = (i + 1 < parts.length) ? parts[i + 1] : '';
const valVN = telexToUnicode(val);
displayParts.push(label + (valVN ? ' ' + valVN : ''));
if (val) i++;
}
}
const title = displayParts.join(' ');
return title || address;
}
// Hiệu ứng typing nhanh cho nội dung kết quả (preview text), sau đó thay bằng HTML đầy đủ
let fastTypingTimerId = null;
function stopFastTypingContent() {
if (fastTypingTimerId) {
clearTimeout(fastTypingTimerId);
fastTypingTimerId = null;
}
}
function stripHtmlToText(html) {
const tmp = document.createElement('div');
tmp.innerHTML = html;
const text = (tmp.textContent || tmp.innerText || '') || '';
return text.replace(/\u00A0/g, ' ');
}
function startFastTypingFinalContent(finalHtml) {
stopThinkingTyping();
stopFastTypingContent();
stopThinkingImages();
const $body = $('#phanTichPanelBody');
if ($body.length === 0) return;
$body.removeClass('thinking-mode');
const previewTextFull = stripHtmlToText(finalHtml).trim();
const maxChars = 800; // giới hạn để không quá lâu
const previewText = previewTextFull.slice(0, maxChars);
$body.html('');
let idx = 0;
const speedMin = 2;
const speedMax = 5;
function typeNext() {
if (idx < previewText.length) {
$('#fastTypingText').append(previewText.charAt(idx));
idx++;
const delay = Math.floor(Math.random() * (speedMax - speedMin + 1)) + speedMin;
fastTypingTimerId = setTimeout(typeNext, delay);
} else {
// Khi gõ xong preview → thay bằng HTML đầy đủ
$body.html(finalHtml);
}
}
typeNext();
}
// Áp dụng hiệu ứng xuất hiện dần từ trên xuống dưới
function applyFadeReveal() {
const $container = $('#phanTichPanelBody .fade-reveal-container');
if (!$container.length) return;
// Lấy các block cấp cao và các phần tử con trong nội dung phân tích
const $blocks = $().add($container.children())
.add($container.find('.phan-tich-content').children());
let delayMs = 0;
const stepMs = 60; // ms giữa các phần tử
$blocks.each(function() {
const $el = $(this);
// Bỏ qua các node text trống
if ($el.prop('nodeType') !== 1) return;
$el.addClass('fade-reveal').css('animation-delay', (delayMs/1000) + 's');
delayMs += stepMs;
});
}
// Nhận diện lỗi quá tải model (503/overloaded) - phạm vi toàn cục
function isOverloadedMessage(msg) {
if (!msg) return false;
const s = String(msg).toLowerCase();
return s.includes('overloaded') || s.includes('unavailable') || s.includes('503');
}
function openPhanTichModal(address, vbID) {
// Tạo modal nếu chưa có
if ($('#modalPhanTich').length === 0) {
const modalHTML = `
Đang phân tích...
Đang phân tích...
`;
$('body').append(modalHTML);
}
// Reset và hiển thị modal với loading
$('#modalPhanTichBody').html(`
Đang phân tích...
Đang phân tích...
`);
$('#modalPhanTich').modal('show');
// AJAX request với retry tối đa 50 lần khi quá tải
(function requestModal(attempt) {
attempt = attempt || 1;
const randomServer = Math.floor(Math.random() * 10) + 1;
$.ajax({
url: '//tnpl' + randomServer + '.hethongphapluat.com/tien-ich/phan.tich.dieu.luat.php',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
address: address,
vb_id: vbID
}),
success: function(response) {
if (response && response.ok) {
let html = '';
html += '';
html += '
' + escapeHtml(response.ten_van_ban) + ' ';
if (response.so_hieu) {
html += 'Số hiệu: ' + escapeHtml(response.so_hieu) + ' ';
}
html += 'Điều khoản: ' + escapeHtml(response.address) + ' ';
html += '
';
html += '' + markdownToHtml(response.phan_tich) + '
';
html += '';
html += 'Những thông tin em vừa cung cấp chỉ mang tính chất tham khảo, không đại diện cho tư vấn chính thức của luật sư. Quý khách nên tìm đến sự tư vấn trực tiếp từ Luật sư hoặc đơn vị pháp lý có chuyên môn để được hỗ trợ cụ thể cho trường hợp của mình.';
html += '
';
$('#modalPhanTichBody').html(html);
} else {
const msg = response && response.error ? response.error : '';
if (isOverloadedMessage(msg) && attempt < 50) {
const delay = Math.min(1200 + attempt * 100, 5000);
setTimeout(function(){ requestModal(attempt + 1); }, delay);
return;
}
if (isOverloadedMessage(msg)) {
$('#modalPhanTichBody').html(`
Hiện tại A.I đang bị quá tải , vui lòng thử lại sau ít phút!
Thử lại
`);
$(document).off('click.tryAgainModal').on('click.tryAgainModal', '#btnTryAgainModal', function(){
openPhanTichModal(address, vbID);
});
} else {
$('#modalPhanTichBody').html(`
Lỗi! ${escapeHtml(msg || 'Không thể phân tích điều luật.')}
Vui lòng thử lại sau.
`);
}
}
},
error: function(xhr, status, error) {
let errorMsg = error;
if (xhr.responseJSON && xhr.responseJSON.error) {
errorMsg = (xhr.responseJSON.error.message || xhr.responseJSON.error) || errorMsg;
} else if (xhr.responseText) {
errorMsg = xhr.responseText;
}
if ((xhr.status === 503 || isOverloadedMessage(errorMsg)) && attempt < 50) {
const delay = Math.min(1200 + attempt * 100, 5000);
setTimeout(function(){ requestModal(attempt + 1); }, delay);
return;
}
if (xhr.status === 503 || isOverloadedMessage(errorMsg)) {
$('#modalPhanTichBody').html(`
Hiện tại A.I đang bị quá tải , vui lòng thử lại sau ít phút!
Thử lại
`);
$(document).off('click.tryAgainModal').on('click.tryAgainModal', '#btnTryAgainModal', function(){
openPhanTichModal(address, vbID);
});
} else {
$('#modalPhanTichBody').html(`
Lỗi! Không thể kết nối đến server phân tích.
Chi tiết: ${escapeHtml(errorMsg)}
`);
}
}
});
})(1);
}
// Helpers: show/hide badge cho parent element (dieu, khoan,...) với position: fixed
function showPhanTichBadgeForParent($parent) {
// Lấy badge CỦA CHÍNH parent này (match data-for với address của parent)
const parentAddress = $parent.attr('address');
const $badge = $parent.find('.badge-phan-tich-container[data-for="' + parentAddress + '"]').first();
if ($badge.length === 0) {
return;
}
// Ẩn TẤT CẢ các badge khác để tránh overlap
$('.badge-phan-tich-container').not($badge).each(function() {
const $otherBadge = $(this);
// Chỉ ẩn badge KHÔNG đang analyzing
if (!$otherBadge.data('analyzing')) {
$otherBadge.css({display: 'none'});
}
});
// Show badge tạm để tính width
$badge.css({display: 'inline-block', opacity: 0, visibility: 'hidden'});
const badgeWidth = $badge.outerWidth();
// Tính toán vị trí fixed dựa trên offset của parent
const offset = $parent.offset();
const scrollTop = $(window).scrollTop();
const scrollLeft = $(window).scrollLeft();
// Position badge top-right của parent và show
$badge.css({
display: 'inline-block',
visibility: 'visible',
opacity: 1,
top: (offset.top - scrollTop) + 'px',
left: (offset.left + $parent.outerWidth() - badgeWidth - scrollLeft - 5) + 'px' // -5px padding
});
$parent.addClass('highlight-border');
}
function hidePhanTichBadgeForParent($parent) {
const $badge = $parent.find('.badge-phan-tich-container').first();
if ($badge.length === 0) return;
$badge.css({display: 'none', opacity: 0});
$parent.removeClass('highlight-border');
}
// Biến lưu element đang hover
let currentHoveredElement = null;
// Dùng mousemove để track chính xác element nào đang được hover (hiển thị ngay lập tức)
$(document).on('mousemove', '#tab_noi_dung_vb', function(e) {
// Nếu đang hover vào thuật ngữ TNPL thì KHÔNG hiển thị badge để tránh che và chặn tooltip
if ($(e.target).closest('tnpl').length > 0) {
// Ẩn các badge không ở trạng thái analyzing
$('.badge-phan-tich-container').each(function(){
const $b = $(this);
if (!$b.data('analyzing') && !$b.hasClass('analyzing')) {
$b.css({display:'none'});
}
});
return;
}
// Tìm element gần nhất (phan, chuong, muc, dieu, khoan, diem) tại vị trí chuột
const $target = $(e.target).closest('phan, chuong, muc, tieumuc, dieu, khoan, diem');
if ($target.length === 0) {
// Không hover vào element nào
return;
}
const address = $target.attr('address');
// Nếu đang hover vào cùng element → skip
if (currentHoveredElement && currentHoveredElement[0] === $target[0]) {
return;
}
// Element thay đổi → xử lý ngay lập tức (không debounce)
// Set flag hovering cho element mới
$target.data('hovering', true);
// Cancel timeout nếu có
const timeoutId = $target.data('hideTimeout');
if (timeoutId) {
clearTimeout(timeoutId);
}
// Ẩn badge của TẤT CẢ elements khác
$('#tab_noi_dung_vb phan, #tab_noi_dung_vb chuong, #tab_noi_dung_vb muc, #tab_noi_dung_vb tieumuc, #tab_noi_dung_vb dieu, #tab_noi_dung_vb khoan, #tab_noi_dung_vb diem')
.not($target)
.each(function() {
const $el = $(this);
// Chỉ xóa highlight-border, KHÔNG xóa highlight-border-persistent
$el.removeClass('highlight-border');
// Ẩn badge nếu KHÔNG đang analyzing
const $badge = $el.find('.badge-phan-tich-container');
if ($badge.length && !$badge.data('analyzing')) {
$badge.css({display: 'none'});
}
});
// Attach badge nếu chưa có
if (address && $target.find('.badge-phan-tich-container[data-for="' + address + '"]').length === 0) {
const parentType = getParentTypeName($target.prop('tagName').toLowerCase());
const extraClass = (unlockAllPhanTich && memberID <= 0) ? ' upgrade-require' : '';
const $badge = $('');
$target.append($badge);
$target.addClass('has-phan-tich-badge');
}
// Show badge cho element này
if ($target.find('.badge-phan-tich-container').length > 0) {
showPhanTichBadgeForParent($target);
}
// Update current hovered element
currentHoveredElement = $target;
});
// Event delegation cho hover ra khỏi #tab_noi_dung_vb
$(document).on('mouseleave', '#tab_noi_dung_vb', function(e) {
// Clear current hovered element
currentHoveredElement = null;
// Ẩn tất cả badge không đang analyzing sau một khoảng thời gian
setTimeout(function() {
if (currentHoveredElement === null) {
// Chỉ ẩn nếu thực sự không hover vào element nào
$('#tab_noi_dung_vb phan, #tab_noi_dung_vb chuong, #tab_noi_dung_vb muc, #tab_noi_dung_vb tieumuc, #tab_noi_dung_vb dieu, #tab_noi_dung_vb khoan, #tab_noi_dung_vb diem')
.each(function() {
const $el = $(this);
const $badge = $el.find('.badge-phan-tich-container');
if ($badge.length && !$badge.data('analyzing')) {
$badge.css({display: 'none'});
}
});
}
}, 3);
});
// Event delegation cho hover ra khỏi parent (giữ lại cho badge behavior)
$(document).on('mouseleave', '#tab_noi_dung_vb phan, #tab_noi_dung_vb chuong, #tab_noi_dung_vb muc, #tab_noi_dung_vb tieumuc, #tab_noi_dung_vb dieu, #tab_noi_dung_vb khoan, #tab_noi_dung_vb diem', function(e) {
const $parent = $(this);
const parentAddress = $parent.attr('address');
const $badge = $parent.find('.badge-phan-tich-container[data-for="' + parentAddress + '"]').first();
// Set flag parent not hovering
$parent.data('hovering', false);
// Nếu badge đang analyzing thì KHÔNG ẩn, GIỮ hiển thị
if ($badge.length > 0 && $badge.data('analyzing')) {
return;
}
// Delay để có thời gian di chuột vào badge
const timeoutId = setTimeout(() => {
// Chỉ ẩn nếu cả parent và badge đều không hover và không analyzing
if ($badge.length > 0 && !$parent.data('hovering') && !$badge.data('hovering') && !$badge.data('analyzing')) {
hidePhanTichBadgeForParent($parent);
}
}, 3); // Tăng lên 300ms
$parent.data('hideTimeout', timeoutId);
});
// Hover vào badge → giữ hiển thị
$(document).on('mouseenter', '.badge-phan-tich-container', function(e) {
e.stopPropagation();
const $badge = $(this);
const $parent = $badge.parent();
$badge.data('hovering', true);
// Cancel timeout của parent
const timeoutId = $parent.data('hideTimeout');
if (timeoutId) {
clearTimeout(timeoutId);
}
});
// Hover ra khỏi badge → ẩn nếu không hover parent
$(document).on('mouseleave', '.badge-phan-tich-container', function(e) {
const $badge = $(this);
$badge.data('hovering', false);
const $parent = $badge.parent();
// Nếu badge đang analyzing thì KHÔNG ẩn, GIỮ hiển thị
if ($badge.data('analyzing') || $badge.hasClass('analyzing')) {
return;
}
setTimeout(() => {
// Chỉ ẩn nếu cả parent và badge đều không hover và không analyzing
if (!$parent.data('hovering') && !$badge.data('hovering') && !$badge.data('analyzing') && !$badge.hasClass('analyzing')) {
hidePhanTichBadgeForParent($parent);
}
}, 3);
});
// Event delegation cho hover vào badge → hiện tooltip
$(document).on('mouseenter', '.badge-phan-tich, .badge-phan-tich-container, .badge-phan-tich-fixed', function() {
const $badge = $(this);
const parentType = $badge.attr('data-parent-type') || 'Nội dung';
if ($badge.find('.badge-tooltip').length === 0) {
const $tooltip = $('Phân tích chi tiết nội dung ' + parentType + ' này ');
$badge.append($tooltip);
setTimeout(() => $tooltip.addClass('show'), 10);
}
});
// Event delegation cho hover ra khỏi badge → ẩn tooltip
$(document).on('mouseleave', '.badge-phan-tich, .badge-phan-tich-container, .badge-phan-tich-fixed', function() {
const $tooltip = $(this).find('.badge-tooltip');
if ($tooltip.length > 0) {
$tooltip.removeClass('show');
setTimeout(() => $tooltip.remove(), 3);
}
});
// Event delegation cho click badge → mở panel
$(document).on('click', '.badge-phan-tich, .badge-phan-tich-container, .badge-phan-tich-fixed', function(e) {
const $badge = $(this);
// Nếu là khách (chưa đăng nhập) sau thời điểm mở khóa → mở modal đăng nhập/mua gói
if (unlockAllPhanTich && memberID <= 0) {
if (!$badge.hasClass('upgrade-require')) {
$badge.addClass('upgrade-require');
}
// Không chặn sự kiện để handler trong modal.content.php bắt và mở modal
return;
}
// Thành viên → mở panel phân tích
e.preventDefault();
e.stopPropagation();
// Nếu badge đang analyzing thì không cho click
if ($badge.hasClass('analyzing') || $badge.data('analyzing')) {
return;
}
// Lấy address từ data-for attribute
const address = $badge.attr('data-for');
if (address && vbID) {
openPhanTichPanel(address, vbID);
} else {
showWarningModal('Không tìm thấy địa chỉ điều luật hoặc ID văn bản!');
}
});
// Ẩn badge khi click vào CTTD
$(document).on('click', 'cttd.chuthichtudong span, dctk span, dctd span', function(e) {
// Ẩn TẤT CẢ badge KHÔNG đang analyzing
$('.badge-phan-tich-container').each(function() {
const $badge = $(this);
if (!$badge.data('analyzing') && !$badge.hasClass('analyzing')) {
$badge.css({display: 'none'});
}
});
});
// Update badge position khi scroll hoặc resize (vì dùng position: fixed)
function updateBadgePositions() {
$('.badge-phan-tich-container:visible').each(function() {
const $badge = $(this);
const $parent = $badge.parent();
// Cập nhật position nếu parent đang hover HOẶC badge đang analyzing
if ($parent.length && ($parent.is(':hover') || $badge.data('analyzing'))) {
// Re-calculate position
const offset = $parent.offset();
const scrollTop = $(window).scrollTop();
const scrollLeft = $(window).scrollLeft();
const badgeWidth = $badge.outerWidth();
$badge.css({
top: (offset.top - scrollTop) + 'px',
left: (offset.left + $parent.outerWidth() - badgeWidth - scrollLeft - 5) + 'px'
});
}
});
}
$(window).on('scroll', updateBadgePositions);
$(window).on('resize', updateBadgePositions);
}
});
Tra cứu thuật ngữ với từ hoặc cụm từ đã chọn?
×
Công văn 1339/BYT-VPB1 năm 2023 về trả lời kiến nghị của cử tri liên quan đến lĩnh vực quản lý của ngành Y tế do Bộ Y tế ban hành