play whole islands, make home the path itself, draw tutor diagrams, add tedi
- Home is now the island path of every unit with one clear Start button on the
current island; the six-card dashboard is gone.
- Start opens /nodes/{id}/quiz (4 questions) instead of one-question lessons, so
an island takes one round instead of ~11. Lessons stay one tap away in the sheet.
- Tutor answers render the backend's validated diagram (plane, number line,
fraction) as React SVG; function expressions go through a small parser, no eval.
- Tedi, the mobile-preview mascot, joins as the fourth companion with all six moods
and an onboarding slide.
- Light polish: 3D buttons, header gradients, warmer page background.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -75,6 +75,15 @@ export const api = {
|
||||
home: () => http.get<T.HomeResponse>('/home'),
|
||||
nodeLessons: (nodeId: string) => http.get<T.NodeLessons>(`/nodes/${nodeId}/lessons`),
|
||||
|
||||
/**
|
||||
* آزمونِ جزیره: از هر درسِ جزیره حداکثر یک سوال (پیشفرض ۴). ترتیب برای هر
|
||||
* کاربر ثابت است، پس رفرش همان آزمون را میدهد.
|
||||
*/
|
||||
nodeQuiz: (nodeId: string) => http.get<T.NodeQuiz>(`/nodes/${nodeId}/quiz`),
|
||||
/** پایانِ آزمون، جزیره را با هر نمرهای تمام میکند و جزیرهی بعدی را باز. */
|
||||
nodeQuizComplete: (nodeId: string, result: { correctCount: number; totalQuestions: number }, idempotencyKey?: string) =>
|
||||
http.create<T.NodeQuizComplete>(`/nodes/${nodeId}/quiz/complete`, result, { idempotencyKey }),
|
||||
|
||||
lesson: {
|
||||
intro: (lessonId: number) => http.get<T.LessonIntro>(`/lessons/${lessonId}/intro`),
|
||||
questions: (lessonId: number) =>
|
||||
|
||||
@@ -121,6 +121,31 @@ export interface Question {
|
||||
options: QuestionOption[];
|
||||
}
|
||||
|
||||
export interface NodeQuizQuestion extends Question {
|
||||
lessonId: string;
|
||||
lessonTitle: string;
|
||||
}
|
||||
|
||||
export interface NodeQuiz {
|
||||
nodeId: string;
|
||||
nodeTitle: string;
|
||||
unitTitle: string;
|
||||
/** ممکن است از requested کمتر باشد (جزیرهی کمسوال). */
|
||||
questionCount: number;
|
||||
requested: number;
|
||||
questions: NodeQuizQuestion[];
|
||||
}
|
||||
|
||||
export interface NodeQuizComplete {
|
||||
nodeId: string;
|
||||
stars: number;
|
||||
xpEarned: number;
|
||||
coinsEarned: number;
|
||||
/** جزیرهی بعدی که حالا باز است؛ null یعنی آخرینِ پایه بود. */
|
||||
nextNodeId: string | null;
|
||||
stats: HomeStats;
|
||||
}
|
||||
|
||||
export interface AnswerResult {
|
||||
correct: boolean;
|
||||
correctOptionId: string;
|
||||
@@ -184,14 +209,36 @@ export interface AskDone {
|
||||
suggestedFollowUps: string[];
|
||||
remainingQuota: number;
|
||||
usedChunks: UsedChunk[];
|
||||
diagram?: Diagram | null;
|
||||
}
|
||||
|
||||
export interface ConversationMessage {
|
||||
messageId: string;
|
||||
role: 'user' | 'assistant' | string;
|
||||
text: string;
|
||||
diagram?: Diagram | null;
|
||||
}
|
||||
|
||||
/* ─── شکلِ پاسخِ دستیار (بکاند: rag/services/diagram.py) ─────────────────── */
|
||||
|
||||
export type DiagramRef = string | [number, number];
|
||||
export type DiagramPointDef = [number, number] | { polar: [number, number]; from?: DiagramRef };
|
||||
|
||||
export type DiagramItem =
|
||||
| { type: 'circle'; center: DiagramRef; r: number; label?: string }
|
||||
| { type: 'segment' | 'arrow'; from: DiagramRef; to: DiagramRef; label?: string }
|
||||
| { type: 'line'; through: [DiagramRef, DiagramRef]; label?: string }
|
||||
| { type: 'angle' | 'rightAngle'; vertex: DiagramRef; from: DiagramRef; to: DiagramRef; label?: string }
|
||||
| { type: 'polygon'; points: DiagramRef[]; label?: string }
|
||||
| { type: 'point'; point: DiagramRef; label?: string }
|
||||
| { type: 'function'; expr: string; from: number; to: number; label?: string }
|
||||
| { type: 'text'; at: [number, number]; text: string };
|
||||
|
||||
export type Diagram =
|
||||
| { kind: 'plane'; title: string; axes: boolean; points: Record<string, DiagramPointDef>; items: DiagramItem[] }
|
||||
| { kind: 'numberline'; title: string; min: number; max: number; points: { at: number; label: string }[]; jumps: { from: number; to: number; label: string }[] }
|
||||
| { kind: 'fraction'; title: string; shape: 'circle' | 'rect'; parts: number; shaded: number };
|
||||
|
||||
export interface NotificationItem {
|
||||
id: number;
|
||||
type: string;
|
||||
|
||||
Reference in New Issue
Block a user