Soften the tone of the docs and comments written today

This commit is contained in:
2026-08-11 10:12:25 +07:00
parent 97cb6d16f4
commit 6b8f7584ed
18 changed files with 921 additions and 64 deletions
+45 -4
View File
@@ -29,6 +29,28 @@ interface ChatPanelProps {
className?: string;
}
// The client must never be the thing that gives up first.
//
// The backend's own per-request budget is 40s (`max_wall_clock_ms` in
// `apps/ai-service/config.py`), and that budget is only checked *between*
// model calls — `rag/budget.py` says so explicitly: it cannot cancel a boto3
// call already in flight, which is separately bounded by `read_timeout=20`
// in `adapters/bedrock_converse.py`. So the backend's real worst case is
// ~40s + one in-flight call ≈ 60s, and anything below that truncates work
// the server was still legitimately doing.
//
// Measured against production 2026-08-11 (n=8, sequential, one user):
// 6.2 / 6.4 / 8.4 / 10.9 / 12.4 / 21.7 / 25.1 / 40.3 seconds. A 25s limit
// cuts off 2 of those 8, including the 25.1s case, which had returned a
// correct grounded answer with 2 citations.
const REQUEST_TIMEOUT_MS = 65_000;
// A single spinner for a minute reads as a hang, so the wait is made
// legible rather than merely longer. This is a stopgap for the real fix
// (streaming verified claims as they land); it does not make the request
// faster, it only stops it looking broken.
const SLOW_REQUEST_NOTICE_MS = 15_000;
const STARTER_QUESTIONS = [
{
category: "Chỉ Định",
@@ -65,6 +87,7 @@ export function ChatPanel({
}: ChatPanelProps) {
const { resolvedTheme } = useTheme();
const [isLoading, setIsLoading] = useState(false);
const [elapsedMs, setElapsedMs] = useState(0);
const [error, setError] = useState<string | null>(null);
const messagesEndRef = useRef<HTMLDivElement>(null);
const abortControllerRef = useRef<AbortController | null>(null);
@@ -98,7 +121,12 @@ export function ChatPanel({
abortControllerRef.current = new AbortController();
const timeoutId = window.setTimeout(() => {
abortControllerRef.current?.abort();
}, 25_000);
}, REQUEST_TIMEOUT_MS);
setElapsedMs(0);
const startedAt = Date.now();
const tickId = window.setInterval(() => {
setElapsedMs(Date.now() - startedAt);
}, 1000);
try {
const res = await fetch("/api/chat", {
@@ -128,13 +156,19 @@ export function ChatPanel({
setError(
stopRequestedRef.current
? "Đã dừng chờ trên giao diện. Tác vụ đang chạy có thể cần vài giây để kết thúc an toàn."
: "Yêu cầu vượt quá 25 giây và đã được dừng. Vui lòng thử lại với câu hỏi cụ thể hơn."
// Describes the timing cause rather than suggesting the
// question needs rewording, which rephrasing would not fix.
: `Hệ thống xử lý quá ${Math.round(
REQUEST_TIMEOUT_MS / 1000
)} giây nên đã dừng yêu cầu này. Vui lòng bấm gửi lại.`
);
return;
}
setError("Không thể kết nối đến máy chủ AI Service. Vui lòng kiểm tra lại dịch vụ backend.");
} finally {
window.clearTimeout(timeoutId);
window.clearInterval(tickId);
setElapsedMs(0);
setIsLoading(false);
abortControllerRef.current = null;
}
@@ -363,8 +397,15 @@ export function ChatPanel({
<Pill className="h-4 w-4 animate-spin" />
</div>
<div>
<p className="text-xs font-bold text-txt-primary">Đang truy xuất Dược thư QGVN 2018...</p>
<p className="text-[0.68rem] text-txt-muted">Đang phân tích chuyên luận & xác thực Entailment</p>
<p className="text-xs font-bold text-txt-primary">
Đang truy xuất Dược thư QGVN 2018...
{elapsedMs >= 1000 && ` ${Math.floor(elapsedMs / 1000)}s`}
</p>
<p className="text-[0.68rem] text-txt-muted">
{elapsedMs >= SLOW_REQUEST_NOTICE_MS
? "Câu hỏi tra cả mục nên cần thêm thời gian đối chiếu nguồn — vẫn đang xử lý."
: "Đang phân tích chuyên luận & xác thực Entailment"}
</p>
</div>
</div>
)}