hydro-ai-helper 1.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +31 -0
- package/.github/workflows/sync-releases-to-gitee.yml +119 -0
- package/.github/workflows/sync-to-gitee.yml +53 -0
- package/.vercelignore +31 -0
- package/CONTRIBUTING.md +165 -0
- package/README.md +273 -0
- package/TROUBLESHOOTING.md +355 -0
- package/api/badge-active.ts +88 -0
- package/api/badge-installs.ts +75 -0
- package/api/report.ts +242 -0
- package/assets/screenshots/1.png +0 -0
- package/assets/screenshots/2.png +0 -0
- package/assets/screenshots/3.png +0 -0
- package/assets/screenshots/4.png +0 -0
- package/assets/screenshots/5.png +0 -0
- package/assets/screenshots/6.png +0 -0
- package/assets/screenshots/7.png +0 -0
- package/dist/__tests__/__mocks__/hydrooj.js +29 -0
- package/dist/__tests__/__mocks__/hydrooj.js.map +1 -0
- package/dist/constants/jailbreakRules.js +29 -0
- package/dist/constants/jailbreakRules.js.map +1 -0
- package/dist/constants/permissions.js +42 -0
- package/dist/constants/permissions.js.map +1 -0
- package/dist/handlers/adminConfigHandler.js +347 -0
- package/dist/handlers/adminConfigHandler.js.map +1 -0
- package/dist/handlers/adminHandler.js +378 -0
- package/dist/handlers/adminHandler.js.map +1 -0
- package/dist/handlers/analyticsHandler.js +433 -0
- package/dist/handlers/analyticsHandler.js.map +1 -0
- package/dist/handlers/dashboardHandler.js +22 -0
- package/dist/handlers/dashboardHandler.js.map +1 -0
- package/dist/handlers/exportHandler.js +118 -0
- package/dist/handlers/exportHandler.js.map +1 -0
- package/dist/handlers/studentHandler.js +430 -0
- package/dist/handlers/studentHandler.js.map +1 -0
- package/dist/handlers/teacherHandler.js +246 -0
- package/dist/handlers/teacherHandler.js.map +1 -0
- package/dist/handlers/testHandler.js +27 -0
- package/dist/handlers/testHandler.js.map +1 -0
- package/dist/handlers/updateHandler.js +84 -0
- package/dist/handlers/updateHandler.js.map +1 -0
- package/dist/handlers/versionHandler.js +61 -0
- package/dist/handlers/versionHandler.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +151 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/crypto.js +110 -0
- package/dist/lib/crypto.js.map +1 -0
- package/dist/lib/httpHelpers.js +61 -0
- package/dist/lib/httpHelpers.js.map +1 -0
- package/dist/lib/queryHelpers.js +81 -0
- package/dist/lib/queryHelpers.js.map +1 -0
- package/dist/models/aiConfig.js +255 -0
- package/dist/models/aiConfig.js.map +1 -0
- package/dist/models/conversation.js +209 -0
- package/dist/models/conversation.js.map +1 -0
- package/dist/models/jailbreakLog.js +68 -0
- package/dist/models/jailbreakLog.js.map +1 -0
- package/dist/models/message.js +174 -0
- package/dist/models/message.js.map +1 -0
- package/dist/models/pluginInstall.js +95 -0
- package/dist/models/pluginInstall.js.map +1 -0
- package/dist/models/rateLimitRecord.js +54 -0
- package/dist/models/rateLimitRecord.js.map +1 -0
- package/dist/models/versionCache.js +89 -0
- package/dist/models/versionCache.js.map +1 -0
- package/dist/services/effectivenessService.js +131 -0
- package/dist/services/effectivenessService.js.map +1 -0
- package/dist/services/exportService.js +152 -0
- package/dist/services/exportService.js.map +1 -0
- package/dist/services/migrationService.js +90 -0
- package/dist/services/migrationService.js.map +1 -0
- package/dist/services/openaiClient.js +412 -0
- package/dist/services/openaiClient.js.map +1 -0
- package/dist/services/promptService.js +467 -0
- package/dist/services/promptService.js.map +1 -0
- package/dist/services/rateLimitService.js +108 -0
- package/dist/services/rateLimitService.js.map +1 -0
- package/dist/services/telemetryService.js +173 -0
- package/dist/services/telemetryService.js.map +1 -0
- package/dist/services/updateService.js +388 -0
- package/dist/services/updateService.js.map +1 -0
- package/dist/services/versionService.js +289 -0
- package/dist/services/versionService.js.map +1 -0
- package/dist/types/hydrooj.js +7 -0
- package/dist/types/hydrooj.js.map +1 -0
- package/dist/types/mongo.js +7 -0
- package/dist/types/mongo.js.map +1 -0
- package/dist/utils/domainHelper.js +16 -0
- package/dist/utils/domainHelper.js.map +1 -0
- package/dist/utils/mongo.js +16 -0
- package/dist/utils/mongo.js.map +1 -0
- package/frontend/admin/ConfigPanel.tsx +1379 -0
- package/frontend/admin/VersionBadge.tsx +655 -0
- package/frontend/ai_helper.page.tsx +52 -0
- package/frontend/ai_helper_admin_config.page.tsx +47 -0
- package/frontend/ai_helper_analytics.page.tsx +52 -0
- package/frontend/ai_helper_conversation_detail.page.tsx +55 -0
- package/frontend/ai_helper_conversations.page.tsx +56 -0
- package/frontend/components/AIHelperDashboard.tsx +125 -0
- package/frontend/problem_detail.page.tsx +395 -0
- package/frontend/student/AIAssistantPanel.tsx +1839 -0
- package/frontend/student/AIChatPanel.tsx +944 -0
- package/frontend/student/ThreeColumnLayout.tsx +240 -0
- package/frontend/teacher/AnalyticsPage.tsx +614 -0
- package/frontend/teacher/ConversationDetail.tsx +504 -0
- package/frontend/teacher/ConversationList.tsx +568 -0
- package/frontend/teacher/ExportDialog.tsx +291 -0
- package/frontend/test.page.ts +88 -0
- package/frontend/utils/domainUtils.ts +37 -0
- package/index.js +6 -0
- package/jest.config.js +17 -0
- package/lib/mongodb.ts +60 -0
- package/locales/en.yaml +4 -0
- package/locales/zh.yaml +77 -0
- package/package.json +48 -0
- package/public/.gitkeep +0 -0
- package/public/README.md +1 -0
- package/src/__tests__/__mocks__/hydrooj.ts +27 -0
- package/src/__tests__/services/exportService.test.ts +156 -0
- package/src/__tests__/services/promptService.test.ts +141 -0
- package/src/__tests__/services/rateLimitService.test.ts +145 -0
- package/src/__tests__/utils/domainHelper.test.ts +47 -0
- package/src/constants/jailbreakRules.ts +28 -0
- package/src/constants/permissions.ts +43 -0
- package/src/handlers/adminConfigHandler.ts +363 -0
- package/src/handlers/adminHandler.ts +427 -0
- package/src/handlers/analyticsHandler.ts +517 -0
- package/src/handlers/dashboardHandler.ts +20 -0
- package/src/handlers/exportHandler.ts +134 -0
- package/src/handlers/studentHandler.ts +483 -0
- package/src/handlers/teacherHandler.ts +317 -0
- package/src/handlers/testHandler.ts +25 -0
- package/src/handlers/updateHandler.ts +85 -0
- package/src/handlers/versionHandler.ts +62 -0
- package/src/index.ts +198 -0
- package/src/lib/crypto.ts +125 -0
- package/src/lib/httpHelpers.ts +75 -0
- package/src/lib/queryHelpers.ts +108 -0
- package/src/models/aiConfig.ts +375 -0
- package/src/models/conversation.ts +328 -0
- package/src/models/jailbreakLog.ts +108 -0
- package/src/models/message.ts +246 -0
- package/src/models/pluginInstall.ts +138 -0
- package/src/models/rateLimitRecord.ts +81 -0
- package/src/models/versionCache.ts +127 -0
- package/src/services/effectivenessService.ts +161 -0
- package/src/services/exportService.ts +191 -0
- package/src/services/migrationService.ts +111 -0
- package/src/services/openaiClient.ts +548 -0
- package/src/services/promptService.ts +573 -0
- package/src/services/rateLimitService.ts +122 -0
- package/src/services/telemetryService.ts +218 -0
- package/src/services/updateService.ts +441 -0
- package/src/services/versionService.ts +351 -0
- package/src/types/hydrooj-extensions.d.ts +55 -0
- package/src/types/hydrooj.ts +38 -0
- package/src/types/mongo.ts +23 -0
- package/src/utils/domainHelper.ts +16 -0
- package/src/utils/mongo.ts +15 -0
- package/templates/ai-helper/admin_config.html +8 -0
- package/templates/ai-helper/analytics.html +8 -0
- package/templates/ai-helper/dashboard.html +8 -0
- package/templates/ai-helper/teacher_conversation_detail.html +8 -0
- package/templates/ai-helper/teacher_conversations.html +8 -0
- package/tests/integration/README.md +86 -0
- package/tests/integration/curl/01-admin-config-set.sh +56 -0
- package/tests/integration/curl/02-student-chat.sh +67 -0
- package/tests/integration/curl/03-teacher-list.sh +52 -0
- package/tests/integration/curl/04-teacher-detail.sh +37 -0
- package/tests/integration/curl/05-export.sh +49 -0
- package/tests/integration/curl/06-analytics.sh +43 -0
- package/tests/performance/README.md +85 -0
- package/tests/performance/data/prepare-sample-data.md +57 -0
- package/tests/performance/scripts/export-csv-k6.js +41 -0
- package/tests/performance/scripts/student-chat-k6.js +44 -0
- package/tests/performance/scripts/teacher-conversations-k6.js +33 -0
- package/tsconfig.json +42 -0
- package/tsconfig.vercel.json +27 -0
- package/types/hydrooj/index.d.ts +73 -0
- package/vercel.json +3 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 学生端发起一次对话,返回 conversationId 并写入 .conversation_id
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
BASE_URL="${HYDRO_BASE_URL:-http://127.0.0.1}"
|
|
6
|
+
COOKIE="${HYDRO_STUDENT_COOKIE:?HYDRO_STUDENT_COOKIE not set. Please export HYDRO_STUDENT_COOKIE=\"sid=...\"}"
|
|
7
|
+
|
|
8
|
+
PROBLEM_ID="${HYDRO_PROBLEM_ID:-P1000}"
|
|
9
|
+
PROBLEM_TITLE="${HYDRO_PROBLEM_TITLE:-Sample Problem}"
|
|
10
|
+
PROBLEM_SUMMARY="${HYDRO_PROBLEM_SUMMARY:-This is a short problem description for testing.}"
|
|
11
|
+
QUESTION_TYPE="${HYDRO_QUESTION_TYPE:-think}" # valid: understand|think|debug|review
|
|
12
|
+
USER_THINKING="${HYDRO_USER_THINKING:-我想用二分解决区间最大值,可以帮我验证思路吗?}"
|
|
13
|
+
INCLUDE_CODE="${HYDRO_CHAT_INCLUDE_CODE:-true}"
|
|
14
|
+
CODE_SNIPPET="${HYDRO_CHAT_CODE:-// sample code: int main(){ return 0; }}"
|
|
15
|
+
|
|
16
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
17
|
+
STATE_FILE="${SCRIPT_DIR}/.conversation_id"
|
|
18
|
+
|
|
19
|
+
echo "[T026] 02-student-chat: create a conversation"
|
|
20
|
+
echo "Using BASE_URL=${BASE_URL}"
|
|
21
|
+
|
|
22
|
+
payload=$(cat <<EOF
|
|
23
|
+
{
|
|
24
|
+
"problemId": "$PROBLEM_ID",
|
|
25
|
+
"problemTitle": "$PROBLEM_TITLE",
|
|
26
|
+
"problemContent": "$PROBLEM_SUMMARY",
|
|
27
|
+
"questionType": "$QUESTION_TYPE",
|
|
28
|
+
"userThinking": "$USER_THINKING",
|
|
29
|
+
"includeCode": $INCLUDE_CODE,
|
|
30
|
+
"code": "$CODE_SNIPPET"
|
|
31
|
+
}
|
|
32
|
+
EOF
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
response=$(curl -sS -w "\nHTTP_STATUS:%{http_code}" -X POST "$BASE_URL/ai-helper/chat" \
|
|
36
|
+
-H "Cookie: $COOKIE" \
|
|
37
|
+
-H "Content-Type: application/json" \
|
|
38
|
+
-d "$payload")
|
|
39
|
+
|
|
40
|
+
body=$(printf "%s" "$response" | sed '/HTTP_STATUS:/d')
|
|
41
|
+
status=$(printf "%s" "$response" | sed -n 's/.*HTTP_STATUS://p')
|
|
42
|
+
echo "$body"
|
|
43
|
+
|
|
44
|
+
if [ "$status" != "200" ]; then
|
|
45
|
+
echo "Unexpected status when chatting: $status" >&2
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
conversation_id=$(printf "%s" "$body" | python3 - <<'PY' || true
|
|
50
|
+
import json, sys
|
|
51
|
+
try:
|
|
52
|
+
data = json.load(sys.stdin)
|
|
53
|
+
cid = data.get("conversationId")
|
|
54
|
+
if cid:
|
|
55
|
+
print(cid)
|
|
56
|
+
except Exception:
|
|
57
|
+
pass
|
|
58
|
+
PY
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
if [ -z "${conversation_id:-}" ]; then
|
|
62
|
+
echo "conversationId not found in response, please check output above." >&2
|
|
63
|
+
exit 1
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
echo "$conversation_id" > "$STATE_FILE"
|
|
67
|
+
echo "[Done] conversationId stored at $STATE_FILE"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 教师/管理员查看对话列表,可附带筛选参数
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
BASE_URL="${HYDRO_BASE_URL:-http://127.0.0.1}"
|
|
6
|
+
COOKIE="${HYDRO_TEACHER_COOKIE:?HYDRO_TEACHER_COOKIE not set. Use a teacher/admin sid.}"
|
|
7
|
+
|
|
8
|
+
# 可选筛选
|
|
9
|
+
START_DATE="${HYDRO_LIST_START_DATE:-}"
|
|
10
|
+
END_DATE="${HYDRO_LIST_END_DATE:-}"
|
|
11
|
+
PROBLEM_ID="${HYDRO_LIST_PROBLEM_ID:-}"
|
|
12
|
+
CLASS_ID="${HYDRO_LIST_CLASS_ID:-}"
|
|
13
|
+
USER_ID="${HYDRO_LIST_USER_ID:-}"
|
|
14
|
+
LIMIT="${HYDRO_LIST_LIMIT:-20}"
|
|
15
|
+
PAGE="${HYDRO_LIST_PAGE:-1}"
|
|
16
|
+
|
|
17
|
+
echo "[T026] 03-teacher-list: fetch conversation list"
|
|
18
|
+
echo "Using BASE_URL=${BASE_URL}"
|
|
19
|
+
|
|
20
|
+
query="page=${PAGE}&limit=${LIMIT}"
|
|
21
|
+
[ -n "$START_DATE" ] && query="${query}&startDate=${START_DATE}"
|
|
22
|
+
[ -n "$END_DATE" ] && query="${query}&endDate=${END_DATE}"
|
|
23
|
+
[ -n "$PROBLEM_ID" ] && query="${query}&problemId=${PROBLEM_ID}"
|
|
24
|
+
[ -n "$CLASS_ID" ] && query="${query}&classId=${CLASS_ID}"
|
|
25
|
+
[ -n "$USER_ID" ] && query="${query}&userId=${USER_ID}"
|
|
26
|
+
|
|
27
|
+
response=$(curl -sS -w "\nHTTP_STATUS:%{http_code}" "$BASE_URL/ai-helper/conversations?${query}" \
|
|
28
|
+
-H "Cookie: $COOKIE" \
|
|
29
|
+
-H "Accept: application/json")
|
|
30
|
+
|
|
31
|
+
body=$(printf "%s" "$response" | sed '/HTTP_STATUS:/d')
|
|
32
|
+
status=$(printf "%s" "$response" | sed -n 's/.*HTTP_STATUS://p')
|
|
33
|
+
echo "$body"
|
|
34
|
+
|
|
35
|
+
if [ "$status" != "200" ]; then
|
|
36
|
+
echo "Unexpected status when listing conversations: $status" >&2
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# Optional negative test for invalid date range
|
|
41
|
+
if [ "${RUN_INVALID_DATE_CHECK:-0}" != "0" ]; then
|
|
42
|
+
invalid_status=$(curl -sS -o /dev/null -w "%{http_code}" "$BASE_URL/ai-helper/conversations?startDate=INVALID_DATE" \
|
|
43
|
+
-H "Cookie: $COOKIE" \
|
|
44
|
+
-H "Accept: application/json")
|
|
45
|
+
echo "Invalid date check status (expected 400): ${invalid_status}"
|
|
46
|
+
if [ "${invalid_status}" != "400" ]; then
|
|
47
|
+
echo "Unexpected status for invalid date: ${invalid_status}" >&2
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
echo "[Done] conversation list fetched."
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 教师/管理员查看对话详情
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
BASE_URL="${HYDRO_BASE_URL:-http://127.0.0.1}"
|
|
6
|
+
COOKIE="${HYDRO_TEACHER_COOKIE:?HYDRO_TEACHER_COOKIE not set. Use a teacher/admin sid.}"
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
+
STATE_FILE="${SCRIPT_DIR}/.conversation_id"
|
|
10
|
+
|
|
11
|
+
CONVERSATION_ID="${HYDRO_CONVERSATION_ID:-}"
|
|
12
|
+
if [ -z "$CONVERSATION_ID" ] && [ -f "$STATE_FILE" ]; then
|
|
13
|
+
CONVERSATION_ID="$(cat "$STATE_FILE")"
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
if [ -z "$CONVERSATION_ID" ]; then
|
|
17
|
+
echo "Missing conversationId. Set HYDRO_CONVERSATION_ID or run 02-student-chat.sh first." >&2
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
echo "[T026] 04-teacher-detail: fetch conversation detail ${CONVERSATION_ID}"
|
|
22
|
+
echo "Using BASE_URL=${BASE_URL}"
|
|
23
|
+
|
|
24
|
+
response=$(curl -sS -w "\nHTTP_STATUS:%{http_code}" "$BASE_URL/ai-helper/conversations/${CONVERSATION_ID}" \
|
|
25
|
+
-H "Cookie: $COOKIE" \
|
|
26
|
+
-H "Accept: application/json")
|
|
27
|
+
|
|
28
|
+
body=$(printf "%s" "$response" | sed '/HTTP_STATUS:/d')
|
|
29
|
+
status=$(printf "%s" "$response" | sed -n 's/.*HTTP_STATUS://p')
|
|
30
|
+
echo "$body"
|
|
31
|
+
|
|
32
|
+
if [ "$status" != "200" ]; then
|
|
33
|
+
echo "Unexpected status when fetching detail: $status" >&2
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
echo "[Done] conversation detail fetched."
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 导出 CSV,默认写入临时文件
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
BASE_URL="${HYDRO_BASE_URL:-http://127.0.0.1}"
|
|
6
|
+
COOKIE="${HYDRO_TEACHER_COOKIE:?HYDRO_TEACHER_COOKIE not set. Use a teacher/admin sid.}"
|
|
7
|
+
|
|
8
|
+
START_DATE="${HYDRO_EXPORT_START_DATE:-}"
|
|
9
|
+
END_DATE="${HYDRO_EXPORT_END_DATE:-}"
|
|
10
|
+
CLASS_ID="${HYDRO_EXPORT_CLASS_ID:-}"
|
|
11
|
+
PROBLEM_ID="${HYDRO_EXPORT_PROBLEM_ID:-}"
|
|
12
|
+
USER_ID="${HYDRO_EXPORT_USER_ID:-}"
|
|
13
|
+
INCLUDE_SENSITIVE="${HYDRO_EXPORT_INCLUDE_SENSITIVE:-false}"
|
|
14
|
+
|
|
15
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
16
|
+
OUTPUT_FILE="${EXPORT_OUTPUT_FILE:-$(mktemp "${SCRIPT_DIR}/export-XXXX.csv")}"
|
|
17
|
+
|
|
18
|
+
echo "[T026] 05-export: export conversations to CSV"
|
|
19
|
+
echo "Using BASE_URL=${BASE_URL}"
|
|
20
|
+
|
|
21
|
+
query="format=csv&includeSensitive=${INCLUDE_SENSITIVE}"
|
|
22
|
+
[ -n "$START_DATE" ] && query="${query}&startDate=${START_DATE}"
|
|
23
|
+
[ -n "$END_DATE" ] && query="${query}&endDate=${END_DATE}"
|
|
24
|
+
[ -n "$CLASS_ID" ] && query="${query}&classId=${CLASS_ID}"
|
|
25
|
+
[ -n "$PROBLEM_ID" ] && query="${query}&problemId=${PROBLEM_ID}"
|
|
26
|
+
[ -n "$USER_ID" ] && query="${query}&userId=${USER_ID}"
|
|
27
|
+
|
|
28
|
+
status=$(curl -sS -o "$OUTPUT_FILE" -w "%{http_code}" "$BASE_URL/ai-helper/export?${query}" \
|
|
29
|
+
-H "Cookie: $COOKIE" \
|
|
30
|
+
-H "Accept: text/csv")
|
|
31
|
+
|
|
32
|
+
if [ "$status" != "200" ]; then
|
|
33
|
+
echo "Unexpected status when exporting: $status" >&2
|
|
34
|
+
echo "Response:" >&2
|
|
35
|
+
cat "$OUTPUT_FILE" >&2
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
echo "Export saved to: $OUTPUT_FILE"
|
|
40
|
+
head -n 5 "$OUTPUT_FILE"
|
|
41
|
+
|
|
42
|
+
if [ "${RUN_INVALID_FORMAT_CHECK:-0}" != "0" ]; then
|
|
43
|
+
invalid_status=$(curl -sS -o /dev/null -w "%{http_code}" "$BASE_URL/ai-helper/export?format=json" \
|
|
44
|
+
-H "Cookie: $COOKIE" \
|
|
45
|
+
-H "Accept: application/json")
|
|
46
|
+
echo "Invalid format check status (expected 400): ${invalid_status}"
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
echo "[Done] export completed."
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 查看统计数据
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
BASE_URL="${HYDRO_BASE_URL:-http://127.0.0.1}"
|
|
6
|
+
COOKIE="${HYDRO_TEACHER_COOKIE:?HYDRO_TEACHER_COOKIE not set. Use a teacher/admin sid.}"
|
|
7
|
+
|
|
8
|
+
DIMENSION="${HYDRO_ANALYTICS_DIMENSION:-problem}" # class|problem|student
|
|
9
|
+
START_DATE="${HYDRO_ANALYTICS_START_DATE:-}"
|
|
10
|
+
END_DATE="${HYDRO_ANALYTICS_END_DATE:-}"
|
|
11
|
+
CLASS_ID="${HYDRO_ANALYTICS_CLASS_ID:-}"
|
|
12
|
+
PROBLEM_ID="${HYDRO_ANALYTICS_PROBLEM_ID:-}"
|
|
13
|
+
|
|
14
|
+
echo "[T026] 06-analytics: fetch analytics (${DIMENSION})"
|
|
15
|
+
echo "Using BASE_URL=${BASE_URL}"
|
|
16
|
+
|
|
17
|
+
query="dimension=${DIMENSION}"
|
|
18
|
+
[ -n "$START_DATE" ] && query="${query}&startDate=${START_DATE}"
|
|
19
|
+
[ -n "$END_DATE" ] && query="${query}&endDate=${END_DATE}"
|
|
20
|
+
[ -n "$CLASS_ID" ] && query="${query}&classId=${CLASS_ID}"
|
|
21
|
+
[ -n "$PROBLEM_ID" ] && query="${query}&problemId=${PROBLEM_ID}"
|
|
22
|
+
|
|
23
|
+
response=$(curl -sS -w "\nHTTP_STATUS:%{http_code}" "$BASE_URL/ai-helper/analytics?${query}" \
|
|
24
|
+
-H "Cookie: $COOKIE" \
|
|
25
|
+
-H "Accept: application/json")
|
|
26
|
+
|
|
27
|
+
body=$(printf "%s" "$response" | sed '/HTTP_STATUS:/d')
|
|
28
|
+
status=$(printf "%s" "$response" | sed -n 's/.*HTTP_STATUS://p')
|
|
29
|
+
echo "$body"
|
|
30
|
+
|
|
31
|
+
if [ "$status" != "200" ]; then
|
|
32
|
+
echo "Unexpected status when fetching analytics: $status" >&2
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
if [ "${RUN_INVALID_DIMENSION_CHECK:-0}" != "0" ]; then
|
|
37
|
+
invalid_status=$(curl -sS -o /dev/null -w "%{http_code}" "$BASE_URL/ai-helper/analytics?dimension=invalid" \
|
|
38
|
+
-H "Cookie: $COOKIE" \
|
|
39
|
+
-H "Accept: application/json")
|
|
40
|
+
echo "Invalid dimension check status (expected 400): ${invalid_status}"
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
echo "[Done] analytics fetched."
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# 性能测试指南(T027)
|
|
2
|
+
|
|
3
|
+
本指南用于在测试环境快速验证 HydroOJ 插件 **@hydrooj/hydro-ai-helper** 的关键接口性能。工具选择保持轻量,默认提供 k6 脚本,也可替换为 `ab`/`wrk` 等等。
|
|
4
|
+
|
|
5
|
+
## 前置条件
|
|
6
|
+
- 已在测试环境部署 HydroOJ 与本插件,核心配置(`ENCRYPTION_KEY`、AI 访问配置等)已填写,并能正常完成对话请求。
|
|
7
|
+
- 拥有测试用 **学生账号 Cookie**(用于 `/ai-helper/chat`),以及 **教师/管理员 Cookie**(用于列表、导出接口)。
|
|
8
|
+
- 本地安装了 [k6](https://k6.io/docs/get-started/installation/);或在说明中使用容器镜像 `grafana/k6` 运行。
|
|
9
|
+
- 测试环境与生产环境隔离,且压力测试期间监控 CPU、内存、MongoDB 慢查询日志。
|
|
10
|
+
|
|
11
|
+
## 测试数据准备
|
|
12
|
+
- 目标数据量(建议):Conversation 数量 1k / 5k / 10k;每个会话约 5-10 条消息。
|
|
13
|
+
- 快速生成数据的两种方式:
|
|
14
|
+
1) 使用学生账号循环调用 `/ai-helper/chat`:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
export HYDRO_BASE_URL=http://127.0.0.1
|
|
18
|
+
export HYDRO_STUDENT_COOKIE="sid=..."
|
|
19
|
+
for i in {1..200}; do
|
|
20
|
+
curl -s -X POST "$HYDRO_BASE_URL/ai-helper/chat" \
|
|
21
|
+
-H 'Content-Type: application/json' \
|
|
22
|
+
-H "Cookie: $HYDRO_STUDENT_COOKIE" \
|
|
23
|
+
-d '{"problemId":"demo","questionType":"understand","userThinking":"我想熟悉这道题的双指针思路"}' >/dev/null
|
|
24
|
+
done
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
2) 在 MongoDB 中批量插入模拟数据:参考 `tests/performance/data/prepare-sample-data.md` 给出的脚本示例。
|
|
28
|
+
|
|
29
|
+
## 关键接口与关注点
|
|
30
|
+
- 学生端高频接口:`POST /ai-helper/chat`
|
|
31
|
+
- 教师端列表/详情:`GET /ai-helper/conversations`、`GET /ai-helper/conversations/:id`
|
|
32
|
+
- 导出接口:`GET /ai-helper/export?format=csv`
|
|
33
|
+
- 统计接口:`GET /ai-helper/analytics`
|
|
34
|
+
|
|
35
|
+
## 压测脚本与运行方式(k6)
|
|
36
|
+
所有脚本位于 `tests/performance/scripts/`。必要环境变量:
|
|
37
|
+
- `HYDRO_BASE_URL`:HydroOJ 基础地址(默认 `http://127.0.0.1`)
|
|
38
|
+
- `HYDRO_STUDENT_COOKIE`:学生登录 Cookie,用于聊天接口
|
|
39
|
+
- `HYDRO_TEACHER_COOKIE`:教师/管理员 Cookie,用于列表/导出接口
|
|
40
|
+
- 可选:`VUS`、`DURATION`、`SLEEP` 用于覆盖并发与节奏
|
|
41
|
+
|
|
42
|
+
1. **学生对话接口**
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
k6 run tests/performance/scripts/student-chat-k6.js \
|
|
46
|
+
--env HYDRO_BASE_URL=http://127.0.0.1 \
|
|
47
|
+
--env HYDRO_STUDENT_COOKIE="sid=..."
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
2. **教师对话列表接口**
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
k6 run tests/performance/scripts/teacher-conversations-k6.js \
|
|
54
|
+
--env HYDRO_BASE_URL=http://127.0.0.1 \
|
|
55
|
+
--env HYDRO_TEACHER_COOKIE="sid=..." \
|
|
56
|
+
--env PAGE_LIMIT=50
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
3. **导出接口(小批量)**
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
k6 run tests/performance/scripts/export-csv-k6.js \
|
|
63
|
+
--env HYDRO_BASE_URL=http://127.0.0.1 \
|
|
64
|
+
--env HYDRO_TEACHER_COOKIE="sid=..." \
|
|
65
|
+
--env EXPORT_DAYS=7
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
> 如果未安装 k6,可使用 Docker:`docker run --rm -i grafana/k6 run - < tests/performance/scripts/student-chat-k6.js`(通过 `-e` 传递环境变量)。
|
|
69
|
+
|
|
70
|
+
## 推荐性能门槛(可按硬件微调)
|
|
71
|
+
- `/ai-helper/chat`:10 并发、30s 压测下,P95 ≤ 2s
|
|
72
|
+
- `/ai-helper/conversations`:10 并发、30s 压测下,P95 ≤ 1s
|
|
73
|
+
- `/ai-helper/export`:在 1k 会话量级内,P95 ≤ 3s
|
|
74
|
+
|
|
75
|
+
满足以上门槛即可视为通过;若不满足,请结合 MongoDB `explain`、慢查询日志定位瓶颈。
|
|
76
|
+
|
|
77
|
+
## 结果解读与记录
|
|
78
|
+
- 关注 k6 输出中的 `http_req_duration`(总耗时)和 `http_req_waiting`(服务端处理耗时)。
|
|
79
|
+
- 以 P95/P99 作为参考;若 P50 与 P95 差距过大,说明尾延迟需要优化。
|
|
80
|
+
- 将测试参数与结果追加记录到 `tests/integration/README.md` 或新建记录文件,便于回溯。
|
|
81
|
+
|
|
82
|
+
## 风险与注意事项
|
|
83
|
+
- 不要在生产环境直接做大流量压测;优先在隔离的测试服执行。
|
|
84
|
+
- 导出接口请从小批量开始(如 7 天或 500~1000 条数据),确认无内存/超时风险后再扩大范围。
|
|
85
|
+
- 压测期间留意 HydroOJ 进程 CPU/内存、MongoDB 连接数和慢查询日志,必要时降低并发或缩短持续时间。
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# 测试数据快速准备示例
|
|
2
|
+
|
|
3
|
+
在测试环境中预先准备一定规模的会话数据可以让性能测试更贴近真实场景。以下两种方式任选其一。
|
|
4
|
+
|
|
5
|
+
## 方式一:使用脚本循环调用聊天接口
|
|
6
|
+
适合需要真实业务链路的场景,可与 `tests/performance/scripts/student-chat-k6.js` 类似:
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
export HYDRO_BASE_URL=http://127.0.0.1
|
|
10
|
+
export HYDRO_STUDENT_COOKIE="sid=..."
|
|
11
|
+
|
|
12
|
+
for i in {1..500}; do
|
|
13
|
+
curl -s -X POST "$HYDRO_BASE_URL/ai-helper/chat" \
|
|
14
|
+
-H 'Content-Type: application/json' \
|
|
15
|
+
-H "Cookie: $HYDRO_STUDENT_COOKIE" \
|
|
16
|
+
-d '{"problemId":"demo","questionType":"understand","userThinking":"第'$i'次压测造数,帮我解释这道题的思路"}' >/dev/null
|
|
17
|
+
sleep 0.2
|
|
18
|
+
done
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 方式二:直接写入 MongoDB(伪造数据)
|
|
22
|
+
适合快速灌入大量会话以测试列表/导出接口。以下为 Mongo shell/`mongosh` 的示例,按需调整集合名与字段:
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
// 在 mongosh 中执行
|
|
26
|
+
const conversations = db.getCollection('ai_conversations');
|
|
27
|
+
const messages = db.getCollection('ai_messages');
|
|
28
|
+
|
|
29
|
+
const now = new Date();
|
|
30
|
+
for (let i = 0; i < 2000; i++) {
|
|
31
|
+
const conversation = {
|
|
32
|
+
userId: 10000 + (i % 50),
|
|
33
|
+
problemId: 'demo',
|
|
34
|
+
classId: 'class-A',
|
|
35
|
+
startTime: new Date(now.getTime() - i * 60000),
|
|
36
|
+
endTime: now,
|
|
37
|
+
messageCount: 6,
|
|
38
|
+
isEffective: true,
|
|
39
|
+
tags: ['loadtest'],
|
|
40
|
+
metadata: { problemTitle: 'A+B Problem' }
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const { insertedId } = conversations.insertOne(conversation);
|
|
44
|
+
|
|
45
|
+
// 插入简化的消息列表
|
|
46
|
+
for (let j = 0; j < 6; j++) {
|
|
47
|
+
messages.insertOne({
|
|
48
|
+
conversationId: insertedId,
|
|
49
|
+
role: j % 2 === 0 ? 'student' : 'ai',
|
|
50
|
+
content: `msg ${j} for conv ${i}`,
|
|
51
|
+
timestamp: new Date(conversation.startTime.getTime() + j * 1000)
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
> 造数后请确认不会影响生产数据,推荐在独立测试数据库中执行。
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import http from 'k6/http';
|
|
2
|
+
import { check, sleep } from 'k6';
|
|
3
|
+
|
|
4
|
+
export const options = {
|
|
5
|
+
vus: parseInt(__ENV.VUS || '2', 10),
|
|
6
|
+
duration: __ENV.DURATION || '20s',
|
|
7
|
+
thresholds: {
|
|
8
|
+
http_req_duration: ['p(95)<3000'],
|
|
9
|
+
checks: ['rate>0.95']
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const BASE_URL = __ENV.HYDRO_BASE_URL || 'http://127.0.0.1';
|
|
14
|
+
const COOKIE = __ENV.HYDRO_TEACHER_COOKIE || __ENV.HYDRO_ADMIN_COOKIE || '';
|
|
15
|
+
const DAYS = parseInt(__ENV.EXPORT_DAYS || '7', 10);
|
|
16
|
+
const SLEEP_SECONDS = parseFloat(__ENV.SLEEP || '1');
|
|
17
|
+
|
|
18
|
+
const endDate = new Date();
|
|
19
|
+
const startDate = new Date(endDate.getTime() - DAYS * 24 * 60 * 60 * 1000);
|
|
20
|
+
|
|
21
|
+
export default function () {
|
|
22
|
+
const url = `${BASE_URL}/ai-helper/export?format=csv&startDate=${encodeURIComponent(
|
|
23
|
+
startDate.toISOString()
|
|
24
|
+
)}&endDate=${encodeURIComponent(endDate.toISOString())}`;
|
|
25
|
+
|
|
26
|
+
const res = http.get(url, {
|
|
27
|
+
headers: {
|
|
28
|
+
Cookie: COOKIE
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
check(res, {
|
|
33
|
+
'status is 200': (r) => r.status === 200,
|
|
34
|
+
'response time < 3s': (r) => r.timings.duration < 3000,
|
|
35
|
+
'has csv content': (r) => (r.headers['Content-Type'] || '').includes('csv')
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (SLEEP_SECONDS > 0) {
|
|
39
|
+
sleep(SLEEP_SECONDS);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import http from 'k6/http';
|
|
2
|
+
import { check, sleep } from 'k6';
|
|
3
|
+
|
|
4
|
+
export const options = {
|
|
5
|
+
vus: parseInt(__ENV.VUS || '10', 10),
|
|
6
|
+
duration: __ENV.DURATION || '30s',
|
|
7
|
+
thresholds: {
|
|
8
|
+
http_req_duration: ['p(95)<2000'],
|
|
9
|
+
checks: ['rate>0.95']
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const BASE_URL = __ENV.HYDRO_BASE_URL || 'http://127.0.0.1';
|
|
14
|
+
const COOKIE = __ENV.HYDRO_STUDENT_COOKIE || '';
|
|
15
|
+
const SLEEP_SECONDS = parseFloat(__ENV.SLEEP || '1');
|
|
16
|
+
|
|
17
|
+
const payload = JSON.stringify({
|
|
18
|
+
problemId: __ENV.PROBLEM_ID || 'demo',
|
|
19
|
+
problemTitle: __ENV.PROBLEM_TITLE || 'Sample Problem',
|
|
20
|
+
problemContent: __ENV.PROBLEM_CONTENT || '给定两个整数 A 和 B,输出它们的和。',
|
|
21
|
+
questionType: 'understand',
|
|
22
|
+
userThinking: __ENV.USER_THINKING || '请帮我理解这道题的双指针/枚举思路。',
|
|
23
|
+
includeCode: false
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const params = {
|
|
27
|
+
headers: {
|
|
28
|
+
'Content-Type': 'application/json',
|
|
29
|
+
Cookie: COOKIE
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default function () {
|
|
34
|
+
const res = http.post(`${BASE_URL}/ai-helper/chat`, payload, params);
|
|
35
|
+
|
|
36
|
+
check(res, {
|
|
37
|
+
'status is 200': (r) => r.status === 200,
|
|
38
|
+
'response time < 2s': (r) => r.timings.duration < 2000
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (SLEEP_SECONDS > 0) {
|
|
42
|
+
sleep(SLEEP_SECONDS);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import http from 'k6/http';
|
|
2
|
+
import { check, sleep } from 'k6';
|
|
3
|
+
|
|
4
|
+
export const options = {
|
|
5
|
+
vus: parseInt(__ENV.VUS || '5', 10),
|
|
6
|
+
duration: __ENV.DURATION || '30s',
|
|
7
|
+
thresholds: {
|
|
8
|
+
http_req_duration: ['p(95)<1000'],
|
|
9
|
+
checks: ['rate>0.95']
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const BASE_URL = __ENV.HYDRO_BASE_URL || 'http://127.0.0.1';
|
|
14
|
+
const COOKIE = __ENV.HYDRO_TEACHER_COOKIE || __ENV.HYDRO_ADMIN_COOKIE || '';
|
|
15
|
+
const PAGE_LIMIT = parseInt(__ENV.PAGE_LIMIT || '50', 10);
|
|
16
|
+
const SLEEP_SECONDS = parseFloat(__ENV.SLEEP || '1');
|
|
17
|
+
|
|
18
|
+
export default function () {
|
|
19
|
+
const res = http.get(`${BASE_URL}/ai-helper/conversations?limit=${PAGE_LIMIT}`, {
|
|
20
|
+
headers: {
|
|
21
|
+
Cookie: COOKIE
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
check(res, {
|
|
26
|
+
'status is 200': (r) => r.status === 200,
|
|
27
|
+
'response time < 1s': (r) => r.timings.duration < 1000
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (SLEEP_SECONDS > 0) {
|
|
31
|
+
sleep(SLEEP_SECONDS);
|
|
32
|
+
}
|
|
33
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": [
|
|
6
|
+
"ES2020"
|
|
7
|
+
],
|
|
8
|
+
"outDir": "./dist",
|
|
9
|
+
"rootDir": "./src",
|
|
10
|
+
"strict": false,
|
|
11
|
+
"noImplicitAny": false,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"forceConsistentCasingInFileNames": false,
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"declaration": false,
|
|
17
|
+
"sourceMap": true,
|
|
18
|
+
"moduleResolution": "node",
|
|
19
|
+
"allowSyntheticDefaultImports": true,
|
|
20
|
+
"experimentalDecorators": true,
|
|
21
|
+
"emitDecoratorMetadata": true,
|
|
22
|
+
"isolatedModules": false,
|
|
23
|
+
"noEmitOnError": false,
|
|
24
|
+
"baseUrl": ".",
|
|
25
|
+
"paths": {
|
|
26
|
+
"hydrooj": [
|
|
27
|
+
"types/hydrooj"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"include": [
|
|
32
|
+
"src/**/*"
|
|
33
|
+
],
|
|
34
|
+
"exclude": [
|
|
35
|
+
"node_modules",
|
|
36
|
+
"dist",
|
|
37
|
+
"src/__tests__"
|
|
38
|
+
],
|
|
39
|
+
"ts-node": {
|
|
40
|
+
"transpileOnly": true
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"target": "ES2020",
|
|
5
|
+
"module": "commonjs",
|
|
6
|
+
"lib": ["ES2020"],
|
|
7
|
+
"outDir": ".vercel/output",
|
|
8
|
+
"rootDir": ".",
|
|
9
|
+
"strict": false,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"moduleResolution": "node",
|
|
15
|
+
"noEmit": false
|
|
16
|
+
},
|
|
17
|
+
"include": [
|
|
18
|
+
"api/**/*",
|
|
19
|
+
"lib/**/*"
|
|
20
|
+
],
|
|
21
|
+
"exclude": [
|
|
22
|
+
"node_modules",
|
|
23
|
+
"src",
|
|
24
|
+
"frontend",
|
|
25
|
+
"dist"
|
|
26
|
+
]
|
|
27
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
declare module 'hydrooj' {
|
|
2
|
+
import { Collection, Db, Cursor } from 'mongodb';
|
|
3
|
+
|
|
4
|
+
export interface Context {
|
|
5
|
+
Route(name: string, path: string, handler: any, privilege?: any): void;
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class Handler {
|
|
10
|
+
ctx: Context;
|
|
11
|
+
request: any;
|
|
12
|
+
response: any;
|
|
13
|
+
user?: any;
|
|
14
|
+
args?: any;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const PRIV: Record<string, any>;
|
|
18
|
+
|
|
19
|
+
export function definePlugin<TConfig = any>(options: {
|
|
20
|
+
name?: string;
|
|
21
|
+
using?: (keyof Context)[];
|
|
22
|
+
schema?: any;
|
|
23
|
+
apply: (ctx: Context, config: TConfig) => void | Promise<void>;
|
|
24
|
+
}): {
|
|
25
|
+
name?: string;
|
|
26
|
+
schema?: any;
|
|
27
|
+
apply: (ctx: Context, config: TConfig) => void | Promise<void>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const Schema: any;
|
|
31
|
+
|
|
32
|
+
export interface DbService {
|
|
33
|
+
collection<T = any>(name: string): Collection<T>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const db: DbService;
|
|
37
|
+
|
|
38
|
+
export const RecordModel: {
|
|
39
|
+
getMulti(domainId: string, query: any): {
|
|
40
|
+
sort(order: any): {
|
|
41
|
+
limit(n: number): {
|
|
42
|
+
project(projection: any): {
|
|
43
|
+
toArray(): Promise<any[]>;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const ProblemModel: any;
|
|
51
|
+
|
|
52
|
+
// STATUS enum from @hydrooj/common, re-exported via builtin
|
|
53
|
+
export const STATUS: {
|
|
54
|
+
STATUS_WAITING: number;
|
|
55
|
+
STATUS_ACCEPTED: number;
|
|
56
|
+
STATUS_WRONG_ANSWER: number;
|
|
57
|
+
STATUS_TIME_LIMIT_EXCEEDED: number;
|
|
58
|
+
STATUS_MEMORY_LIMIT_EXCEEDED: number;
|
|
59
|
+
STATUS_OUTPUT_LIMIT_EXCEEDED: number;
|
|
60
|
+
STATUS_RUNTIME_ERROR: number;
|
|
61
|
+
STATUS_COMPILE_ERROR: number;
|
|
62
|
+
STATUS_SYSTEM_ERROR: number;
|
|
63
|
+
STATUS_CANCELED: number;
|
|
64
|
+
STATUS_ETC: number;
|
|
65
|
+
STATUS_HACKED: number;
|
|
66
|
+
STATUS_JUDGING: number;
|
|
67
|
+
STATUS_COMPILING: number;
|
|
68
|
+
STATUS_FETCHED: number;
|
|
69
|
+
STATUS_IGNORED: number;
|
|
70
|
+
STATUS_FORMAT_ERROR: number;
|
|
71
|
+
[key: string]: number;
|
|
72
|
+
};
|
|
73
|
+
}
|
package/vercel.json
ADDED