Add HF model auto-download with configurable AI env, and share link visibility toggles
This commit is contained in:
@@ -656,7 +656,7 @@ app.get('/api/settings', requireAdmin, async (_, res) => {
|
||||
});
|
||||
|
||||
app.get('/api/public-settings', apiLimiter, async (_, res) => {
|
||||
const keys = ['footer_left', 'footer_right'];
|
||||
const keys = ['footer_left', 'footer_right', 'share_show_student_message', 'share_show_entry_date', 'share_show_student_names', 'share_show_group_photos'];
|
||||
const out = {};
|
||||
for (const k of keys) out[k] = await getSetting(k, '');
|
||||
res.json(out);
|
||||
@@ -715,7 +715,8 @@ const zlib = require('zlib');
|
||||
const tar = require('tar');
|
||||
const os = require('os');
|
||||
const AI_URL = process.env.AI_URL || 'http://text-corrector:8080';
|
||||
const AI_DEFAULT_PROMPT = 'Ты — редактор текстов для педагогического журнала. Исправь грамматические, орфографические и пунктуационные ошибки. Сохрани смысл и стиль. Верни ТОЛЬКО исправленный текст, без пояснений.';
|
||||
const AI_MODEL = process.env.AI_MODEL || 'qwen2.5-1.5b-instruct-q4_k_m.gguf';
|
||||
const AI_DEFAULT_PROMPT = process.env.AI_PROMPT || 'Ты — редактор текстов. Исправь ТОЛЬКО грамматические, орфографические и пунктуационные ошибки в тексте. Приведи к правильному регистру буквы. НЕ меняй слова, структуру предложений, стиль или смысл текста. Верни ТОЛЬКО исправленный текст без пояснений.';
|
||||
let entryAutoChecker = null;
|
||||
|
||||
async function getAiPrompt() {
|
||||
@@ -730,7 +731,7 @@ async function aiCorrectText(text) {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
model: 'qwen2.5-1.5b-instruct',
|
||||
model: AI_MODEL,
|
||||
messages: [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{ role: 'user', content: text }
|
||||
@@ -1148,7 +1149,7 @@ app.get('/api/links', requireAuth, async (req, res) => {
|
||||
});
|
||||
|
||||
app.post('/api/links', requireAuth, async (req, res) => {
|
||||
const { name, group_id, student_name, date_from, date_to, anonymize_names, expires_at, access_password } = req.body;
|
||||
const { name, group_id, student_name, date_from, date_to, expires_at, access_password } = req.body;
|
||||
if (!name?.trim()) return res.status(400).json({ error: 'Название обязательно' });
|
||||
let message, linkUrl;
|
||||
try {
|
||||
@@ -1178,16 +1179,16 @@ app.post('/api/links', requireAuth, async (req, res) => {
|
||||
expiresAt = defaultExp.toISOString();
|
||||
}
|
||||
const { rows } = await pool.query(
|
||||
`INSERT INTO share_links (token, name, group_id, student_name, date_from, date_to, anonymize_names, expires_at, access_password_hash, message, link_url)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING *`,
|
||||
[token, name.trim(), group_id || null, student_name || null, date_from || null, date_to || null, anonymize_names || false, expiresAt, passwordHash, message, linkUrl]
|
||||
`INSERT INTO share_links (token, name, group_id, student_name, date_from, date_to, show_student_names, expires_at, access_password_hash, message, link_url, show_student_message, show_entry_date, show_group_photos)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING *`,
|
||||
[token, name.trim(), group_id || null, student_name || null, date_from || null, date_to || null, req.body.show_student_names == null ? null : !!req.body.show_student_names, expiresAt, passwordHash, message, linkUrl, req.body.show_student_message == null ? null : !!req.body.show_student_message, req.body.show_entry_date == null ? null : !!req.body.show_entry_date, req.body.show_group_photos == null ? null : !!req.body.show_group_photos]
|
||||
);
|
||||
await logAudit(req, 'link.create', { id: rows[0].id, name: name.trim() });
|
||||
res.status(201).json(normDates(rows[0]));
|
||||
});
|
||||
|
||||
app.put('/api/links/:id', requireAuth, async (req, res) => {
|
||||
const { name, group_id, student_name, date_from, date_to, anonymize_names, expires_at, access_password } = req.body;
|
||||
const { name, group_id, student_name, date_from, date_to, expires_at, access_password } = req.body;
|
||||
if (!name?.trim()) return res.status(400).json({ error: 'Название обязательно' });
|
||||
let message, linkUrl;
|
||||
try {
|
||||
@@ -1227,9 +1228,9 @@ app.put('/api/links/:id', requireAuth, async (req, res) => {
|
||||
expiresAt = null; // Clear expiry if null sent
|
||||
}
|
||||
}
|
||||
const fields = ['name = $1', 'group_id = $2', 'student_name = $3', 'date_from = $4', 'date_to = $5', 'anonymize_names = $6', 'message = $7', 'link_url = $8'];
|
||||
const values = [name.trim(), group_id || null, student_name || null, date_from || null, date_to || null, anonymize_names || false, message, linkUrl];
|
||||
let paramIdx = 9;
|
||||
const fields = ['name = $1', 'group_id = $2', 'student_name = $3', 'date_from = $4', 'date_to = $5', 'show_student_names = $6', 'message = $7', 'link_url = $8', 'show_student_message = $9', 'show_entry_date = $10', 'show_group_photos = $11'];
|
||||
const values = [name.trim(), group_id || null, student_name || null, date_from || null, date_to || null, req.body.show_student_names == null ? null : !!req.body.show_student_names, message, linkUrl, req.body.show_student_message == null ? null : !!req.body.show_student_message, req.body.show_entry_date == null ? null : !!req.body.show_entry_date, req.body.show_group_photos == null ? null : !!req.body.show_group_photos];
|
||||
let paramIdx = 12;
|
||||
if (passwordHash !== undefined) {
|
||||
fields.push(`access_password_hash = $${paramIdx++}`);
|
||||
values.push(passwordHash);
|
||||
@@ -1311,8 +1312,10 @@ app.get('/api/share/:token', fileLimiter, async (req, res) => {
|
||||
}
|
||||
entries.forEach(r => { r.files = files[r.id] || []; });
|
||||
|
||||
// Anonymize names if enabled
|
||||
if (l.anonymize_names) {
|
||||
const showStudentNames = l.show_student_names != null ? l.show_student_names : (await getSetting('share_show_student_names', 'true')) !== 'false';
|
||||
|
||||
// Anonymize names if disabled
|
||||
if (!showStudentNames) {
|
||||
const nameMap = new Map();
|
||||
let counter = 1;
|
||||
entries.forEach(e => {
|
||||
@@ -1342,9 +1345,13 @@ app.get('/api/share/:token', fileLimiter, async (req, res) => {
|
||||
message: l.message,
|
||||
link_url: l.link_url,
|
||||
created_at: l.created_at,
|
||||
anonymize_names: l.anonymize_names,
|
||||
expires_at: l.expires_at,
|
||||
show_student_names: showStudentNames,
|
||||
show_student_message: l.show_student_message != null ? l.show_student_message : (await getSetting('share_show_student_message', 'false')) === 'true',
|
||||
show_entry_date: l.show_entry_date != null ? l.show_entry_date : (await getSetting('share_show_entry_date', 'false')) === 'true',
|
||||
show_group_photos: l.show_group_photos != null ? l.show_group_photos : (await getSetting('share_show_group_photos', 'true')) !== 'false',
|
||||
entries,
|
||||
photos,
|
||||
photos: (l.show_group_photos != null ? l.show_group_photos : (await getSetting('share_show_group_photos', 'true')) !== 'false') ? photos : [],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2589,7 +2596,7 @@ app.get('/api/ai/queue', requireAdmin, async (_, res) => {
|
||||
enabled,
|
||||
counts,
|
||||
worker: entryAutoChecker ? entryAutoChecker.getStats() : null,
|
||||
model: process.env.AI_MODEL || 'qwen2.5-1.5b-instruct',
|
||||
model: AI_MODEL,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2642,7 +2649,7 @@ app.get('/api/ai/status', requireAdmin, async (_, res) => {
|
||||
const service = await aiHealthCheck();
|
||||
res.json({
|
||||
enabled,
|
||||
model: process.env.AI_MODEL || 'qwen2.5-1.5b-instruct',
|
||||
model: AI_MODEL,
|
||||
ai_url: AI_URL,
|
||||
service,
|
||||
worker: entryAutoChecker ? entryAutoChecker.getInfo() : null,
|
||||
|
||||
Reference in New Issue
Block a user