Skip to content
This repository was archived by the owner on Oct 15, 2025. It is now read-only.

Commit 32cc177

Browse files
committed
refactor(challenge/instruction): Improve hint prompts and code
1 parent 322a8a1 commit 32cc177

File tree

6 files changed

+97
-95
lines changed

6 files changed

+97
-95
lines changed

src/Twig/Components/Challenge/Instruction/Content.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
namespace App\Twig\Components\Challenge\Instruction;
66

7-
use Symfony\Component\Serializer\SerializerInterface;
87
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
98
use Symfony\UX\LiveComponent\Attribute\LiveArg;
109
use Symfony\UX\LiveComponent\Attribute\LiveListener;
@@ -17,12 +16,15 @@ final class Content
1716
use DefaultActionTrait;
1817

1918
#[LiveProp(writable: true)]
20-
public ?HintPayload $hint = null;
19+
public ?string $type = null;
20+
21+
#[LiveProp(writable: true)]
22+
public ?string $hint = null;
2123

2224
#[LiveListener('app:challenge-hint')]
23-
public function onHintReceived(SerializerInterface $serializer, #[LiveArg] string $hint): void
25+
public function onHintReceived(#[LiveArg] string $type, #[LiveArg] string $hint): void
2426
{
25-
$deserializedHint = $serializer->deserialize($hint, HintPayload::class, 'json');
26-
$this->hint = $deserializedHint;
27+
$this->type = $type;
28+
$this->hint = $hint;
2729
}
2830
}

src/Twig/Components/Challenge/Instruction/HintPayload.php

Lines changed: 0 additions & 51 deletions
This file was deleted.

src/Twig/Components/Challenge/Instruction/Modal.php

Lines changed: 64 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,37 @@
66

77
use App\Entity\HintOpenEvent;
88
use App\Entity\Question;
9+
use App\Entity\SolutionEventStatus;
910
use App\Entity\User;
1011
use App\Repository\SolutionEventRepository;
12+
use App\Service\DbRunnerComparer;
1113
use App\Service\DbRunnerService;
1214
use App\Service\PointCalculationService;
1315
use App\Service\PromptService;
1416
use Doctrine\ORM\EntityManagerInterface;
1517
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
1618
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
17-
use Symfony\Component\Serializer\SerializerInterface;
19+
use Symfony\Component\Translation\TranslatableMessage;
1820
use Symfony\Contracts\Translation\TranslatorInterface;
1921
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
2022
use Symfony\UX\LiveComponent\Attribute\LiveAction;
2123
use Symfony\UX\LiveComponent\Attribute\LiveProp;
2224
use Symfony\UX\LiveComponent\ComponentToolsTrait;
2325
use Symfony\UX\LiveComponent\DefaultActionTrait;
2426

27+
use function Symfony\Component\Translation\t;
28+
2529
#[AsLiveComponent]
2630
final class Modal
2731
{
2832
use ComponentToolsTrait;
2933
use DefaultActionTrait;
3034

35+
public function __construct(
36+
private readonly TranslatorInterface $translator,
37+
) {
38+
}
39+
3140
#[LiveProp]
3241
public User $currentUser;
3342

@@ -50,7 +59,6 @@ public function instruct(
5059
DbRunnerService $dbRunnerService,
5160
PromptService $promptService,
5261
TranslatorInterface $translator,
53-
SerializerInterface $serializer,
5462
EntityManagerInterface $entityManager,
5563
ParameterBagInterface $parameterBag,
5664
): void {
@@ -63,49 +71,77 @@ public function instruct(
6371

6472
$query = $solutionEventRepository->getLatestQuery($this->question, $this->currentUser);
6573
if (null === $query) {
74+
$this->flushHint('informative', t('instruction.hint.not_submitted'));
75+
6676
return;
6777
}
78+
if (SolutionEventStatus::Passed === $query->getStatus()) {
79+
$this->flushHint('informative', t('instruction.hint.solved'));
6880

69-
$schema = $this->question->getSchema()->getSchema();
70-
$answer = $this->question->getAnswer();
81+
return;
82+
}
7183

72-
$hintOpenEvent = (new HintOpenEvent())
73-
->setOpener($this->currentUser)
74-
->setQuestion($this->question)
75-
->setQuery($query->getQuery());
84+
$schema = $query->getQuestion()->getSchema();
7685

77-
// run answer. if it failed, we should consider it an error
7886
try {
79-
$answerResult = $dbRunnerService->runQuery($schema, $answer);
87+
$answer = $query->getQuestion()->getAnswer();
88+
$answerResult = $dbRunnerService->runQuery($schema->getSchema(), $answer);
8089
} catch (\Throwable $e) {
81-
$this->emit('app:challenge-hint', [
82-
'hint' => $serializer->serialize(HintPayload::fromError($e->getMessage()), 'json'),
83-
]);
90+
$this->flushHint('informative', t('instruction.hint.error', [
91+
'%error%' => $e->getMessage(),
92+
]));
8493

8594
return;
8695
}
8796

97+
$hintOpenEvent = (new HintOpenEvent())
98+
->setOpener($this->currentUser)
99+
->setQuestion($this->question)
100+
->setQuery($query->getQuery());
101+
88102
try {
89-
// run query to get the error message (or compare the result)
90-
$result = $dbRunnerService->runQuery($schema, $query->getQuery());
91-
} catch (\Throwable $e) {
92-
$hint = $promptService->hint($query->getQuery(), $e->getMessage(), $answer);
93-
}
103+
try {
104+
$userResult = $dbRunnerService->runQuery($schema->getSchema(), $query->getQuery());
105+
} catch (\Throwable $e) {
106+
$hint = $promptService->hint($query->getQuery(), $e->getMessage(), $answer);
107+
$hintOpenEvent->setResponse($hint);
94108

95-
if (isset($result) && $result !== $answerResult) {
96-
$hint = $promptService->hint($query->getQuery(), 'Different output', $answer);
97-
}
109+
$this->flushHint('hint', $hint);
110+
111+
return;
112+
}
113+
114+
$compareResult = DbRunnerComparer::compare($answerResult, $userResult);
115+
if ($compareResult->correct()) {
116+
$this->flushHint('informative', t('instruction.hint.solved'));
117+
118+
return;
119+
}
98120

99-
if (!isset($hint)) {
100-
$hint = $translator->trans('instruction.hint.no_hint');
121+
$compareReason = $compareResult->reason()->trans($translator, 'en_US');
122+
$hint = $promptService->hint($query->getQuery(), "Different result: {$compareReason}", $answer);
123+
$hintOpenEvent->setResponse($hint);
124+
125+
$this->flushHint('hint', $hint);
126+
} finally {
127+
$entityManager->persist($hintOpenEvent);
128+
$entityManager->flush();
101129
}
130+
}
102131

132+
/**
133+
* Flush the hint to the client.
134+
*
135+
* @param string $type the type of the hint (informative or hint)
136+
* @param string|TranslatableMessage $hint the hint to flush
137+
*/
138+
private function flushHint(string $type, string|TranslatableMessage $hint): void
139+
{
103140
$this->emit('app:challenge-hint', [
104-
'hint' => $serializer->serialize(HintPayload::fromHint($hint), 'json'),
141+
'type' => $type,
142+
'hint' => $hint instanceof TranslatableMessage
143+
? $hint->trans($this->translator)
144+
: $hint,
105145
]);
106-
107-
$hintOpenEvent = $hintOpenEvent->setResponse($hint);
108-
$entityManager->persist($hintOpenEvent);
109-
$entityManager->flush();
110146
}
111147
}
Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
<div{{ attributes }}>
2-
{% if hint and hint.error %}
3-
<div class="alert alert-danger" role="alert">
4-
<h4 class="alert-heading">無法取得提示</h4>
5-
<p>{{ hint.error }}</p>
6-
</div>
7-
{% elseif hint and hint.hint %}
8-
<div class="alert alert-info" role="alert">
9-
<h4 class="alert-heading">提示</h4>
10-
<p>{{ hint.hint|markdown_to_html }}</p>
11-
</div>
2+
{% if hint %}
3+
{% if type == 'informative' %}
4+
<div class="alert alert-warning" role="alert">
5+
{{ hint }}
6+
</div>
7+
{% elseif type == 'hint' %}
8+
<div class="alert alert-info" role="alert">
9+
<h4 class="alert-heading">提示</h4>
10+
<p>{{ hint|markdown_to_html }}</p>
11+
</div>
12+
{% else %}
13+
<div class="alert alert-danger" role="alert">
14+
<p>發現一個未知類型的提示({{ type }}:{{ hint }})</p>
15+
</div>
16+
{% endif %}
1217
{% endif %}
1318
</div>

translations/messages.en_US.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
challenge:
2+
compare-result:
3+
same: The answers are completely identical.
4+
empty-answer: The correct answer has no fields, which usually indicates that the query statement written by the questioner is incorrect. Please report this to us.
5+
empty-result: Your answer has no fields, which usually indicates that the query statement is incorrect.
6+
column-different: The column names differ; please compare and modify according to the correct answer.
7+
row-different: The %row% row you answered is different from the correct answer.
8+
row-unmatched: The number of returned rows does not match the correct answer (the correct answer has %expected% rows, while you answered %actual% rows).

translations/messages.zh_TW.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ charts:
9898

9999
instruction:
100100
hint:
101-
no_hint: 沒有提示。
101+
not_submitted: 提交答案之後才能請 GPT 提示。
102+
solved: 正確答案不需要提示。
103+
error: 無法取得正確答案:%error%
102104

103105
feedback:
104106
type:

0 commit comments

Comments
 (0)