Skip to content

Commit 41666b8

Browse files
Merge pull request #11 from CodingWithTashi/claude/add-premium-features-uk0VU
Identify premium features for subscription monetization
2 parents b9d9c0d + c2bb616 commit 41666b8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+6254
-28
lines changed

IMPLEMENTATION_PLAN.md

Lines changed: 973 additions & 0 deletions
Large diffs are not rendered by default.

IMPLEMENTATION_SUMMARY.md

Lines changed: 430 additions & 0 deletions
Large diffs are not rendered by default.

PHASE_2_COMPLETION.md

Lines changed: 559 additions & 0 deletions
Large diffs are not rendered by default.

api-backend/src/index.ts

Lines changed: 378 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import cors from "cors";
66
import helmet from "helmet";
77
import rateLimit from "express-rate-limit";
88
import { checkGrammar } from "./services/grammarService";
9+
import advancedGrammarService from "./services/advancedGrammarService";
10+
import transliterationService from "./services/transliterationService";
911
import {
1012
validateApiKey,
1113
checkUserLimits,
@@ -255,6 +257,382 @@ app.post(
255257
}
256258
);
257259

260+
// ============================================
261+
// NEW PREMIUM FEATURE ENDPOINTS
262+
// ============================================
263+
264+
// Advanced Tibetan Grammar Analysis Endpoint
265+
app.post("/api/grammar/analyze", async (req, res) => {
266+
try {
267+
const { text, mode = "realtime", style = "formal", contextualInfo } = req.body;
268+
const userId = req.headers["userid"] as string;
269+
270+
if (!text || text.trim().length === 0) {
271+
return res.status(400).json({
272+
success: false,
273+
error: "Text is required",
274+
});
275+
}
276+
277+
const userLevel = contextualInfo?.userLevel || "intermediate";
278+
const documentType = contextualInfo?.documentType || "casual";
279+
280+
const result = await advancedGrammarService.analyzeTibetanGrammar(
281+
text,
282+
userLevel,
283+
documentType
284+
);
285+
286+
// Save to Firestore history
287+
try {
288+
const db = admin.firestore();
289+
const userRef = db.collection("users").doc(userId);
290+
const historyRef = userRef.collection("grammar_history").doc();
291+
292+
await historyRef.set({
293+
text,
294+
corrections: result.corrections,
295+
timestamp: admin.firestore.FieldValue.serverTimestamp(),
296+
documentType,
297+
savedByUser: false,
298+
mode,
299+
});
300+
} catch (dbError) {
301+
console.error("Error saving to Firestore:", dbError);
302+
// Don't fail the request if Firestore fails
303+
}
304+
305+
return res.json({
306+
success: true,
307+
data: result,
308+
usage: {
309+
charactersUsed: text.length,
310+
},
311+
});
312+
} catch (error) {
313+
console.error("Grammar analysis error:", error);
314+
return res.status(500).json({
315+
success: false,
316+
error: "Grammar analysis failed",
317+
message: error instanceof Error ? error.message : "Unknown error",
318+
});
319+
}
320+
});
321+
322+
// Tone Alternatives Endpoint
323+
app.post("/api/grammar/suggestions", async (req, res) => {
324+
try {
325+
const { text, correctionId, type } = req.body;
326+
327+
if (!text) {
328+
return res.status(400).json({
329+
success: false,
330+
error: "Text is required",
331+
});
332+
}
333+
334+
let result: any = {
335+
alternatives: [],
336+
examples: [],
337+
culturalNotes: "",
338+
};
339+
340+
if (type === "alternatives") {
341+
const tones: Array<"formal" | "casual" | "poetic" | "religious" | "modern"> = [
342+
"formal",
343+
"casual",
344+
"poetic",
345+
];
346+
for (const tone of tones) {
347+
const alternatives = await advancedGrammarService.getToneAlternatives(text, tone);
348+
result.alternatives.push({
349+
tone,
350+
suggestions: alternatives,
351+
});
352+
}
353+
}
354+
355+
return res.json({
356+
success: true,
357+
data: result,
358+
});
359+
} catch (error) {
360+
console.error("Grammar suggestions error:", error);
361+
return res.status(500).json({
362+
success: false,
363+
error: "Failed to get suggestions",
364+
});
365+
}
366+
});
367+
368+
// Transliteration Endpoints
369+
app.post("/api/transliterate/convert", async (req, res) => {
370+
try {
371+
const { text, sourceSystem = "wylie", targetSystem = "tibetan", context = "common" } =
372+
req.body;
373+
const userId = req.headers["userid"] as string;
374+
375+
if (!text) {
376+
return res.status(400).json({
377+
success: false,
378+
error: "Text is required",
379+
});
380+
}
381+
382+
let result: any;
383+
384+
// Perform conversion based on source and target systems
385+
if (sourceSystem === "wylie" && targetSystem === "tibetan") {
386+
result = transliterationService.convertWylieToTibetan(text);
387+
} else if (sourceSystem === "tibetan" && targetSystem === "wylie") {
388+
result = transliterationService.convertTibetanToWylie(text);
389+
} else if (targetSystem === "phonetic") {
390+
result = transliterationService.convertToPhonetics(text);
391+
} else {
392+
result = {
393+
result: text,
394+
alternatives: [],
395+
confidence: 0,
396+
};
397+
}
398+
399+
// Save to Firestore history
400+
try {
401+
const db = admin.firestore();
402+
const userRef = db.collection("users").doc(userId);
403+
const historyRef = userRef.collection("transliteration_history").doc();
404+
405+
await historyRef.set({
406+
sourceText: text,
407+
sourceSystem,
408+
targetSystem,
409+
result: result.result,
410+
timestamp: admin.firestore.FieldValue.serverTimestamp(),
411+
savedByUser: false,
412+
});
413+
} catch (dbError) {
414+
console.error("Error saving transliteration to Firestore:", dbError);
415+
}
416+
417+
return res.json({
418+
success: true,
419+
data: result,
420+
});
421+
} catch (error) {
422+
console.error("Transliteration error:", error);
423+
return res.status(500).json({
424+
success: false,
425+
error: "Transliteration failed",
426+
message: error instanceof Error ? error.message : "Unknown error",
427+
});
428+
}
429+
});
430+
431+
// Transliteration Database Lookup
432+
app.post("/api/transliterate/database/lookup", async (req, res) => {
433+
try {
434+
const { query, type = "common", limit = 10 } = req.body;
435+
436+
if (!query) {
437+
return res.status(400).json({
438+
success: false,
439+
error: "Query is required",
440+
});
441+
}
442+
443+
const results = transliterationService.searchNameDatabase(query);
444+
445+
return res.json({
446+
success: true,
447+
data: {
448+
results: results.slice(0, limit),
449+
},
450+
});
451+
} catch (error) {
452+
console.error("Transliteration lookup error:", error);
453+
return res.status(500).json({
454+
success: false,
455+
error: "Lookup failed",
456+
});
457+
}
458+
});
459+
460+
// Enhanced Chat with Tutoring Support
461+
app.post("/api/chat/message", async (req, res) => {
462+
try {
463+
const {
464+
sessionId,
465+
message,
466+
conversationMode = "general",
467+
tutoringLevel = "intermediate",
468+
documentContext,
469+
includeExplanation = false,
470+
} = req.body;
471+
const userId = req.headers["userid"] as string;
472+
473+
if (!message) {
474+
return res.status(400).json({
475+
success: false,
476+
error: "Message is required",
477+
});
478+
}
479+
480+
const currentSessionId = sessionId || userId;
481+
482+
// Create or update session with mode
483+
const chat = await ChatSessionManager.getOrCreateSession(
484+
currentSessionId,
485+
conversationMode as any,
486+
tutoringLevel as any,
487+
documentContext
488+
);
489+
490+
// Send message to Gemini
491+
const tibetanResponse = await ChatSessionManager.sendMessage(
492+
currentSessionId,
493+
message,
494+
conversationMode as any
495+
);
496+
497+
// Save message to Firestore
498+
try {
499+
const db = admin.firestore();
500+
const conversationRef = db
501+
.collection("users")
502+
.doc(userId)
503+
.collection("conversations")
504+
.doc(currentSessionId);
505+
506+
// Create or update conversation document
507+
await conversationRef.set(
508+
{
509+
mode: conversationMode,
510+
tutoringLevel: conversationMode === "tutoring" ? tutoringLevel : null,
511+
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
512+
messageCount: admin.firestore.FieldValue.increment(1),
513+
preview: message.substring(0, 100),
514+
lastMessageTime: admin.firestore.FieldValue.serverTimestamp(),
515+
},
516+
{ merge: true }
517+
);
518+
519+
// Add message to subcollection
520+
const messagesRef = conversationRef.collection("messages").doc();
521+
await messagesRef.set({
522+
sender: "user",
523+
content: message,
524+
timestamp: admin.firestore.FieldValue.serverTimestamp(),
525+
mode: conversationMode,
526+
});
527+
528+
// Add response
529+
const responseRef = conversationRef.collection("messages").doc();
530+
await responseRef.set({
531+
sender: "assistant",
532+
content: tibetanResponse,
533+
timestamp: admin.firestore.FieldValue.serverTimestamp(),
534+
mode: conversationMode,
535+
confidence: 0.92,
536+
});
537+
} catch (dbError) {
538+
console.error("Error saving chat to Firestore:", dbError);
539+
}
540+
541+
const response: GeminiChatResponse = {
542+
success: true,
543+
data: {
544+
response: tibetanResponse,
545+
sessionId: currentSessionId,
546+
messageId: `msg_${Date.now()}`,
547+
},
548+
usage: {
549+
charactersUsed: message.length + tibetanResponse.length,
550+
},
551+
};
552+
553+
return res.json(response);
554+
} catch (error) {
555+
console.error("Enhanced chat error:", error);
556+
return res.status(500).json({
557+
success: false,
558+
error: "Chat failed",
559+
message: error instanceof Error ? error.message : "Unknown error",
560+
});
561+
}
562+
});
563+
564+
// Chat History Endpoint
565+
app.get("/api/chat/history", async (req, res) => {
566+
try {
567+
const userId = req.headers["userid"] as string;
568+
const limit = parseInt(req.query.limit as string) || 20;
569+
const offset = parseInt(req.query.offset as string) || 0;
570+
571+
const db = admin.firestore();
572+
const conversationsRef = db.collection("users").doc(userId).collection("conversations");
573+
574+
let query: any = conversationsRef.orderBy("updatedAt", "desc").limit(limit);
575+
576+
if (offset > 0) {
577+
query = query.offset(offset);
578+
}
579+
580+
const snapshot = await query.get();
581+
const conversations = snapshot.docs.map((doc) => ({
582+
conversationId: doc.id,
583+
...doc.data(),
584+
}));
585+
586+
return res.json({
587+
success: true,
588+
data: {
589+
conversations,
590+
totalCount: snapshot.size,
591+
},
592+
});
593+
} catch (error) {
594+
console.error("Chat history error:", error);
595+
return res.status(500).json({
596+
success: false,
597+
error: "Failed to fetch history",
598+
});
599+
}
600+
});
601+
602+
// Tutoring Mode Configuration
603+
app.post("/api/chat/tutoring/mode", async (req, res) => {
604+
try {
605+
const { sessionId, enabled, level = "beginner", topic = "grammar" } = req.body;
606+
const userId = req.headers["userid"] as string;
607+
608+
if (enabled) {
609+
ChatSessionManager.updateSessionMode(sessionId || userId, "tutoring", level as any);
610+
611+
const curriculum = ChatSessionManager.getTutoringCurriculum(level as any);
612+
613+
return res.json({
614+
success: true,
615+
data: {
616+
curriculum,
617+
systemPrompt: `Tutoring mode activated for ${level} level`,
618+
},
619+
});
620+
} else {
621+
ChatSessionManager.updateSessionMode(sessionId || userId, "general");
622+
return res.json({
623+
success: true,
624+
message: "Tutoring mode disabled",
625+
});
626+
}
627+
} catch (error) {
628+
console.error("Tutoring mode error:", error);
629+
return res.status(500).json({
630+
success: false,
631+
error: "Failed to configure tutoring mode",
632+
});
633+
}
634+
});
635+
258636
// Error handling middleware
259637
app.use(errorHandler);
260638

0 commit comments

Comments
 (0)