initial commit
This commit is contained in:
BIN
src/public/favicon-192.png
Normal file
BIN
src/public/favicon-192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
src/public/favicon-32.png
Normal file
BIN
src/public/favicon-32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src/public/favicon-512.png
Normal file
BIN
src/public/favicon-512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
34
src/public/favicon.svg
Normal file
34
src/public/favicon.svg
Normal file
@@ -0,0 +1,34 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#4a6cf7"/>
|
||||
<stop offset="100%" style="stop-color:#6c8eff"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="globe" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#ffffff;stop-opacity:0.95"/>
|
||||
<stop offset="100%" style="stop-color:#e0e7ff;stop-opacity:0.9"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Rounded square background -->
|
||||
<rect width="512" height="512" rx="112" fill="url(#bg)"/>
|
||||
|
||||
<!-- Globe circle -->
|
||||
<circle cx="232" cy="272" r="140" fill="none" stroke="url(#globe)" stroke-width="14"/>
|
||||
|
||||
<!-- Globe meridians -->
|
||||
<ellipse cx="232" cy="272" rx="60" ry="140" fill="none" stroke="url(#globe)" stroke-width="10"/>
|
||||
<ellipse cx="232" cy="272" rx="110" ry="140" fill="none" stroke="url(#globe)" stroke-width="8" opacity="0.5"/>
|
||||
|
||||
<!-- Globe parallels -->
|
||||
<line x1="92" y1="220" x2="372" y2="220" stroke="url(#globe)" stroke-width="9" opacity="0.7"/>
|
||||
<line x1="92" y1="272" x2="372" y2="272" stroke="url(#globe)" stroke-width="9"/>
|
||||
<line x1="92" y1="324" x2="372" y2="324" stroke="url(#globe)" stroke-width="9" opacity="0.7"/>
|
||||
|
||||
<!-- Speech bubble -->
|
||||
<rect x="270" y="80" width="170" height="110" rx="24" fill="white" opacity="0.95"/>
|
||||
<polygon points="290,190 310,190 280,220" fill="white" opacity="0.95"/>
|
||||
|
||||
<!-- "de" text in speech bubble -->
|
||||
<text x="355" y="152" font-family="Arial, Helvetica, sans-serif" font-size="62" font-weight="700" fill="#4a6cf7" text-anchor="middle">de</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
1112
src/public/index.html
Normal file
1112
src/public/index.html
Normal file
File diff suppressed because it is too large
Load Diff
79
src/server.js
Normal file
79
src/server.js
Normal file
@@ -0,0 +1,79 @@
|
||||
const express = require("express");
|
||||
const path = require("path");
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 8083;
|
||||
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
||||
|
||||
app.use(express.json());
|
||||
app.use(express.static(path.join(__dirname, "public")));
|
||||
|
||||
const LEVEL_DESCRIPTIONS = {
|
||||
A2: "базовый (A2): будь снисходителен к ошибкам, хвали за попытку, указывай только грубые ошибки",
|
||||
B1: "средний (B1): указывай основные грамматические ошибки и предлагай улучшения",
|
||||
B2: "выше среднего (B2): анализируй детально — грамматику, стиль, лексику, естественность речи",
|
||||
};
|
||||
|
||||
app.post("/api/check", async (req, res) => {
|
||||
const { question, answer, level } = req.body;
|
||||
|
||||
if (!question || !answer) {
|
||||
return res.status(400).json({ error: "question and answer are required" });
|
||||
}
|
||||
|
||||
if (!ANTHROPIC_API_KEY) {
|
||||
return res.status(500).json({ error: "API key not configured" });
|
||||
}
|
||||
|
||||
const levelDesc = LEVEL_DESCRIPTIONS[level] || LEVEL_DESCRIPTIONS["B1"];
|
||||
|
||||
const systemPrompt = `Ты помощник для практики разговорного немецкого языка. Уровень пользователя: ${levelDesc}.
|
||||
|
||||
ВАЖНО: ответ пользователя получен через автоматическое распознавание речи (speech-to-text), поэтому в нём могут быть неправильные заглавные буквы, отсутствовать знаки препинания или быть опечатки — это артефакты распознавания, НЕ ошибки пользователя. Никогда не упоминай и не исправляй заглавные буквы, пунктуацию и орфографию.
|
||||
|
||||
Тебе дают вопрос на немецком и ответ пользователя на немецком. Твоя задача:
|
||||
|
||||
1. **Оценка** — кратко оцени ответ (1-2 предложения)
|
||||
2. **Ошибки** — перечисли грамматические/лексические ошибки с исправлениями (если есть)
|
||||
3. **Улучшенная версия** — предложи более естественную/правильную формулировку ответа
|
||||
4. **Полезные слова и фразы** — 2-4 слова или выражения по теме с переводом
|
||||
|
||||
Отвечай на русском языке. Будь конструктивным. Используй markdown для форматирования.`;
|
||||
|
||||
const userMessage = `Вопрос: ${question}\n\nОтвет пользователя: ${answer}`;
|
||||
|
||||
try {
|
||||
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": ANTHROPIC_API_KEY,
|
||||
"anthropic-version": "2023-06-01",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "claude-haiku-4-5-20251001",
|
||||
max_tokens: 1024,
|
||||
system: systemPrompt,
|
||||
messages: [{ role: "user", content: userMessage }],
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
console.error("Anthropic API error:", response.status, error);
|
||||
return res.status(502).json({ error: "AI service error" });
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const feedback = data.content?.[0]?.text || "";
|
||||
|
||||
res.json({ feedback });
|
||||
} catch (err) {
|
||||
console.error("Request failed:", err);
|
||||
res.status(500).json({ error: "Internal server error" });
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server running on http://localhost:${PORT}`);
|
||||
});
|
||||
Reference in New Issue
Block a user