Add data-driven dynamic question mode

This commit is contained in:
hz
2026-07-23 12:33:23 -05:00
parent 50f07a77a5
commit fcbc288a49
22 changed files with 1247 additions and 21 deletions
+30
View File
@@ -33,6 +33,36 @@ const CORE_MIN_TARGET_SECONDS := 45.0
const CORE_MAX_TARGET_SECONDS := 150.0
const CORE_RETRY_TARGET_SECONDS := 30.0
func start_dynamic_question(question: Dictionary) -> bool:
if question.is_empty():
return false
system_name = str(question.get("system", "All Systems"))
mode = "Tutor"
session_kind = "dynamic"
active_questions = [_prepare_question_choices(question)]
answers = [null]
current_index = 0
seconds_remaining = 0.0
session_initial_seconds = 90.0
current_question_started_at = Time.get_ticks_msec() / 1000.0
current_question_pause_started_at = 0.0
block_started = true
session_changed.emit()
return true
func replace_dynamic_question(question: Dictionary) -> bool:
if session_kind != "dynamic" or question.is_empty():
return false
active_questions = [_prepare_question_choices(question)]
answers = [null]
current_index = 0
current_question_started_at = Time.get_ticks_msec() / 1000.0
current_question_pause_started_at = 0.0
session_changed.emit()
return true
func start_block(selected_system: String, block_size: int, selected_mode: String) -> void:
system_name = selected_system
mode = selected_mode
@@ -0,0 +1,108 @@
extends Node
const EndpointConfig := preload("res://scripts/autoload/api_endpoint_config.gd")
const DEFAULT_API_URL := "https://us3q.ranaze.com/api/dynamic-questions"
signal status_loaded(status: Dictionary)
signal question_loaded(question: Dictionary)
signal attempt_completed(result: Dictionary)
signal report_submitted(question_id: String, report_id: String)
signal report_failed(question_id: String, message: String)
signal request_failed(message: String)
var api_url := DEFAULT_API_URL
var run_token := ""
var generation_token := ""
var current_envelope := {}
var _request: HTTPRequest
var _operation := ""
var _report_question_id := ""
func _ready() -> void:
api_url = EndpointConfig.resolve(
str(ProjectSettings.get_setting("step3/dynamic_questions_api_url", DEFAULT_API_URL)),
"/api/dynamic-questions")
_request = HTTPRequest.new()
_request.timeout = 30.0
_request.request_completed.connect(_on_completed)
add_child(_request)
func load_status() -> void:
_send("status", HTTPClient.METHOD_GET, "/status", {})
func load_next() -> void:
_send("next", HTTPClient.METHOD_POST, "/next", {"runToken": run_token})
func submit_attempt(choice_id: String, duration_seconds: int, is_skip := false) -> void:
_send("attempt", HTTPClient.METHOD_POST, "/attempt", {
"generationToken": generation_token,
"choiceId": choice_id,
"isSkip": is_skip,
"durationSeconds": duration_seconds
})
func submit_report(question_id: String, category: String, details: String) -> void:
_report_question_id = question_id
_send("report", HTTPClient.METHOD_POST, "/report", {
"generationToken": generation_token,
"category": category,
"details": details
})
func reset_run() -> void:
run_token = ""
generation_token = ""
current_envelope = {}
func _send(operation: String, method: int, suffix: String, payload: Dictionary) -> void:
if not AuthState.can_send_authenticated_request():
request_failed.emit("Log in to use Dynamic Questions.")
return
if _request.get_http_client_status() != HTTPClient.STATUS_DISCONNECTED:
return
_operation = operation
var headers := PackedStringArray([
"Content-Type: application/json",
"Authorization: Bearer %s" % AuthState.access_token
])
var body := "" if method == HTTPClient.METHOD_GET else JSON.stringify(payload)
var error := _request.request(api_url + suffix, headers, method, body)
if error != OK:
_operation = ""
request_failed.emit("Could not connect to Dynamic Questions.")
func _on_completed(_result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
var operation := _operation
_operation = ""
var parsed: Variant = JSON.parse_string(body.get_string_from_utf8())
if response_code < 200 or response_code >= 300 or typeof(parsed) != TYPE_DICTIONARY:
var message := str(parsed) if parsed != null else "Dynamic Questions request failed."
if operation == "report":
report_failed.emit(_report_question_id, message)
else:
request_failed.emit(message)
return
var data: Dictionary = parsed
match operation:
"status":
status_loaded.emit(data)
"next":
run_token = str(data.get("runToken", ""))
generation_token = str(data.get("generationToken", ""))
current_envelope = data
var question := QuestionBankService.normalize_api_question(data.get("question", {}))
question["_dynamic_objective_id"] = str(data.get("objectiveId", ""))
question["_dynamic_objective_title"] = str(data.get("objectiveTitle", ""))
question_loaded.emit(question)
"attempt":
attempt_completed.emit(data)
"report":
report_submitted.emit(_report_question_id, str(data.get("id", "")))
@@ -0,0 +1 @@
uid://b8u2b8km7k6t