Download src/lib/api/tutor.ts from asnannp/DocDoeAI: direct link, hf CLI and curl.
- Browser
- Download file 4.94 kB
-
https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/lib/api/tutor.ts
- Command line
-
hf download hf://spaces/asnannp/DocDoeAI/src/lib/api/tutor.ts
-
curl -L -o tutor.ts https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/lib/api/tutor.ts
4.94 kB
| import { askDocDoe } from "@/lib/api/brain"; | |
| import { getPublicDemoMode } from "@/lib/env"; | |
| import { hasLiveSession } from "@/lib/api/mode"; | |
| import type { BrainAskMode, BrainAskResponse, DocumentChunk, StudentLevel } from "@/lib/api/types"; | |
| export type DocDoeAnswer = { | |
| title: string; | |
| summary: string; | |
| points: string[]; | |
| examTip: string; | |
| citations: string[]; | |
| evidenceLabel?: string | null; | |
| }; | |
| type AskDocDoeInput = { | |
| documentId: string; | |
| sourceTitle: string; | |
| sourceIds?: string[]; | |
| sourceTitles?: string[]; | |
| question: string; | |
| language?: string; | |
| level?: StudentLevel; | |
| mode?: string; | |
| }; | |
| function createFallbackAnswer(input: AskDocDoeInput, chunks: DocumentChunk[]): DocDoeAnswer { | |
| const bestChunk = chunks[0]?.chunk_text; | |
| return { | |
| title: `Study insight: ${input.question.slice(0, 42)}${input.question.length > 42 ? "..." : ""}`, | |
| summary: bestChunk | |
| ? `From ${input.sourceTitle}: ${bestChunk}` | |
| : `Choose a ready source and DocDoe will answer from your material. For now, focus on the core idea and turn it into short exam points.`, | |
| points: chunks.slice(0, 3).map((chunk) => chunk.heading || chunk.chunk_text).filter(Boolean), | |
| examTip: "Exam tip: write the definition first, then add keywords, then finish with one clean example or diagram if needed.", | |
| citations: chunks.length > 0 ? chunks.map((chunk) => chunk.heading || input.sourceTitle).slice(0, 3) : [input.sourceTitle], | |
| }; | |
| } | |
| function isExamPrompt(question: string) { | |
| const text = question.toLowerCase(); | |
| return text.includes("mark") || text.includes("exam") || text.includes("answer") || text.includes("pyq"); | |
| } | |
| function askModeFromPrompt(question: string, explicitMode?: string): BrainAskMode { | |
| if (explicitMode === "explain_simple" || explicitMode === "exam_answer" || explicitMode === "pyq_pattern" || explicitMode === "summarize" || explicitMode === "diagram_help") { | |
| return explicitMode; | |
| } | |
| const text = question.toLowerCase(); | |
| if (text.includes("pyq") || text.includes("pattern")) return "pyq_pattern"; | |
| if (isExamPrompt(question)) return "exam_answer"; | |
| if (text.includes("summar") || text.includes("notes") || text.includes("points")) return "summarize"; | |
| if (text.includes("diagram")) return "diagram_help"; | |
| return "explain_simple"; | |
| } | |
| function brainAnswerToDocDoeAnswer(input: AskDocDoeInput, response: BrainAskResponse): DocDoeAnswer { | |
| const sectionPoints = response.sections | |
| .flatMap((section) => section.body.split(/\n+/).map((line) => line.replace(/^[-*\d.\s]+/, "").trim())) | |
| .filter(Boolean); | |
| const sourceTitleById = new Map<string, string>(); | |
| input.sourceIds?.forEach((id, index) => { | |
| const title = input.sourceTitles?.[index]; | |
| if (title) sourceTitleById.set(id, title); | |
| }); | |
| const citations = response.citations | |
| .map((citation) => citation.source_title || (citation.source_id ? sourceTitleById.get(citation.source_id) : null) || citation.heading || citation.snippet) | |
| .filter((citation): citation is string => Boolean(citation)); | |
| return { | |
| title: response.sections[0]?.title || `DocDoe answer: ${input.question.slice(0, 42)}${input.question.length > 42 ? "..." : ""}`, | |
| summary: response.answer, | |
| points: sectionPoints.length > 0 ? sectionPoints.slice(0, 6) : response.suggested_actions.slice(0, 4), | |
| examTip: response.trust_note || "Use keywords from your source and keep the answer point-wise.", | |
| citations: citations.length > 0 ? Array.from(new Set(citations)).slice(0, 6) : input.sourceTitles?.slice(0, 5) ?? [input.sourceTitle], | |
| evidenceLabel: response.evidence_label ?? null, | |
| }; | |
| } | |
| export async function askDocDoeFromSource(input: AskDocDoeInput): Promise<DocDoeAnswer> { | |
| const sourceIds = input.sourceIds?.length ? input.sourceIds : [input.documentId]; | |
| if (getPublicDemoMode() === "live" && !hasLiveSession()) { | |
| // Only block if no session at all — local sources still work via fallback | |
| if (sourceIds.length === 0) { | |
| throw new Error("Sign in to use real DocDoe answers."); | |
| } | |
| } | |
| if (input.question.toLowerCase().includes("pyq") && !input.sourceTitle.toLowerCase().includes("pyq")) { | |
| const fallback = createFallbackAnswer(input, []); | |
| return { | |
| ...fallback, | |
| title: `Likely questions from this material`, | |
| summary: "No question papers uploaded yet. Here are exam-style questions from your uploaded material instead.", | |
| points: [ | |
| "Upload a previous question paper to unlock PYQ pattern analysis.", | |
| ...fallback.points.slice(0, 3), | |
| ], | |
| evidenceLabel: "Based on uploaded material only", | |
| }; | |
| } | |
| const response = await askDocDoe({ | |
| question: input.question, | |
| mode: askModeFromPrompt(input.question, input.mode), | |
| source_id: input.documentId, | |
| source_ids: sourceIds, | |
| level: input.level ?? "intermediate", | |
| language_preference: input.language ?? "English", | |
| }); | |
| return brainAnswerToDocDoeAnswer(input, response); | |
| } | |