-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.ts
More file actions
155 lines (137 loc) · 5.41 KB
/
Copy pathserver.ts
File metadata and controls
155 lines (137 loc) · 5.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import express from "express";
import path from "path";
import { GoogleGenAI } from "@google/genai";
const hasApiKey = !!process.env.GEMINI_API_KEY;
let ai: GoogleGenAI | null = null;
if (hasApiKey) {
ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY! });
}
const offlineTips: Record<string, string[]> = {
ar: [
'نصيحة الخبير: ركز على وضع أصابعك بشكل صحيح على صف الارتكاز قبل البدء.',
'نصيحة الخبير: الدقة أهم من السرعة. أبطئ وركز على كل حرف.',
'نصيحة الخبير: خذ نفساً عميقاً واسترخِ كتفيك قبل البدء بالتمرين.',
'نصيحة الخبير: لا تنظر إلى لوحة المفاتيح! ثق بذاكرتك العضلية.',
'نصيحة الخبير: التكرار هو أم المهارة. أعد التمرين حتى تتقنه.',
],
en: [
'Expert tip: Focus on accuracy first, speed will come naturally with practice.',
'Expert tip: Keep your fingers curved and relaxed on the home row.',
'Expert tip: Take regular breaks every 20 minutes to prevent fatigue.',
'Expert tip: Practice daily for 15 minutes to build muscle memory.',
'Expert tip: Sit up straight with your screen at eye level.',
]
};
let tipIndex = 0;
async function getLocalTip(language: string): Promise<string> {
const lang = language === 'ar' ? 'ar' : 'en';
const tips = offlineTips[lang] || offlineTips.ar;
const tip = tips[tipIndex % tips.length];
tipIndex++;
return tip;
}
async function startServer() {
const app = express();
const PORT = 3000;
app.use(express.json());
// API Routes
app.post("/api/quick-tip", async (req, res) => {
try {
const { lessonTitle, lessonContent, language } = req.body;
if (!hasApiKey || !ai) {
const tip = await getLocalTip(language || 'ar');
return res.json({ tip });
}
const prompt = `أنت خبير محترف وموجه للطباعة باللمس.
قم بإعطاء نصيحة احترافية وسريعة وقصيرة جداً (لا تتجاوز جملتين) للطالب الذي يدرس الدرس التالي:
عنوان الدرس: ${lessonTitle}
محتوى الدرس: ${lessonContent}
لغة الدرس: ${language === 'ar' ? 'العربية' : 'الإنجليزية'}
اجعل النصيحة ذكية، ملهمة، ومركزة على وضعية الجلوس أو حركة الأصابع أو العامل النفسي، وابدأها بـ "نصيحة الخبير: ".`;
const response = await ai.models.generateContent({
model: 'gemini-2.5-flash',
contents: prompt
});
res.json({ tip: response.text });
} catch (error) {
console.error('Error generating tip:', error);
const tip = await getLocalTip(req.body?.language || 'ar');
res.json({ tip });
}
});
// Vite middleware for development
if (process.env.NODE_ENV !== "production") {
const { createServer: createViteServer } = await import("vite");
const vite = await createViteServer({
server: { middlewareMode: true },
appType: "spa",
});
app.use(vite.middlewares);
} else {
const distPath = path.join(process.cwd(), 'dist');
// Check for embedded assets (standalone EXE mode)
let embedded: Record<string, string> | null = null;
try {
embedded = require(path.join(__dirname, '_embedded.cjs'));
} catch (e1) {
try {
embedded = require('./_embedded.cjs');
} catch {}
}
if (embedded && Object.keys(embedded).length > 0) {
const mimeTypes: Record<string, string> = {
'.html': 'text/html; charset=utf-8',
'.js': 'application/javascript; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
'.ttf': 'font/ttf',
'.webmanifest': 'application/manifest+json',
'.map': 'application/json',
};
app.get('*', (req, res) => {
let filePath = req.path === '/' ? 'index.html' : req.path.replace(/^\//, '');
let content = embedded![filePath];
if (!content) {
const idxFallback = filePath + 'index.html';
content = embedded![idxFallback];
}
if (!content) {
content = embedded!['index.html'];
}
if (content) {
const ext = path.extname(filePath) || '.html';
const mime = mimeTypes[ext] || 'application/octet-stream';
const buf = Buffer.from(content, 'base64');
res.set('Content-Type', mime);
res.set('Content-Length', String(buf.length));
if (ext === '.html') {
res.set('Cache-Control', 'no-cache');
}
res.send(buf);
} else {
res.status(404).send('Not found');
}
});
} else {
app.use(express.static(distPath));
app.get('*all', (req, res) => {
res.sendFile(path.join(distPath, 'index.html'));
});
}
}
app.listen(PORT, "0.0.0.0", () => {
console.log(`TypoMaster server running on http://localhost:${PORT}`);
if (!hasApiKey) {
console.log(' Running in offline mode: GEMINI_API_KEY not set. Using local tips.');
}
});
}
startServer();