import { readFileSync } from 'node:fs'; import { isAdvertising } from './chat-policy.mjs'; export { moderateChatMessage } from './chat-policy.mjs'; export const categories = { urgent: 'Срочно', normal: 'Не срочно', spam: 'Спам' }; export const topics = { learning: 'Учёба и вебинары', file: 'Открытие файла', access: 'Права доступа', bug: 'Сообщение о баге', other: 'Другое', spam: 'Спам', }; export const spamRecommendation = 'Проверьте обращение. Если это спам, закройте его без ответа.'; const systemPrompt = readFileSync(new URL('./triage-prompt.md', import.meta.url), 'utf8'); const resultSchema = { type: 'object', additionalProperties: false, required: ['category', 'topic', 'studentImpact', 'needsReview', 'reason', 'draft'], properties: { category: { type: 'string', enum: Object.keys(categories) }, topic: { type: 'string', enum: Object.keys(topics) }, studentImpact: { type: 'boolean' }, needsReview: { type: 'boolean' }, reason: { type: 'string' }, draft: { type: 'string' }, }, }; export function maskContactDetails(text) { return text .replace(/[\p{L}\d._%+-]+@[\p{L}\d.-]+\.[\p{L}]{2,}/gu, '[email скрыт]') .replace(/(? {}); throw new Error(`ИИ-сервис вернул ошибку HTTP ${response.status}. Проверьте настройки API и повторите обработку.`); } let result; try { const responseBody = await response.json(); const content = responseBody.choices?.[0]?.message?.content; result = JSON.parse(content); } catch { throw new Error('ИИ-сервис вернул ответ, который не удалось прочитать. Повторите обработку.'); } if (!result || typeof result.category !== 'string' || !Object.hasOwn(categories, result.category) || typeof result.topic !== 'string' || !Object.hasOwn(topics, result.topic) || typeof result.studentImpact !== 'boolean' || typeof result.needsReview !== 'boolean' || typeof result.reason !== 'string' || !result.reason.trim() || result.reason.length > 450 || typeof result.draft !== 'string' || !result.draft.trim() || result.draft.length > 2500) { throw new Error('Ответ ИИ не прошёл проверку формата. Повторите обработку.'); } return { category: result.category, topic: result.category === 'spam' ? 'spam' : result.topic === 'spam' ? 'other' : result.topic, studentImpact: actor === 'staff' && result.category !== 'spam' && result.studentImpact, needsReview: result.topic === 'access' || result.needsReview, reason: maskContactDetails(result.reason.trim()), draft: result.category === 'spam' ? spamRecommendation : maskContactDetails(result.draft.trim()), }; } return { mode, provider, model, classify }; }