This commit is contained in:
Krikorios
2025-07-12 22:36:47 +04:00
parent 7fbf089ec5
commit f83b27db61
73 changed files with 19053 additions and 189 deletions
+73
View File
@@ -0,0 +1,73 @@
import { NextResponse } from 'next/server';
// Mock data for demo purposes
const mockAudits = [
{
id: '1',
fileName: 'homepage-banner.jpg',
originalAltText: '',
suggestedAltText: 'University campus building with students walking in the foreground',
wcagScore: 85,
improvements: [
'Add descriptive alt text to improve accessibility',
'Ensure sufficient color contrast',
'Consider adding captions for better understanding'
],
createdAt: new Date('2024-12-01T10:00:00Z'),
userId: '1'
},
{
id: '2',
fileName: 'course-diagram.png',
originalAltText: 'diagram',
suggestedAltText: 'Flow chart showing course prerequisites with arrows connecting related subjects',
wcagScore: 92,
improvements: [
'Current alt text is good',
'Consider adding more descriptive details',
'Ensure text is readable at all zoom levels'
],
createdAt: new Date('2024-12-02T14:30:00Z'),
userId: '1'
}
];
export async function GET() {
try {
// Return mock data instead of database query
return NextResponse.json(mockAudits);
} catch (error) {
console.error('Error fetching accessibility audits:', error);
return NextResponse.json({ error: 'Failed to fetch audits' }, { status: 500 });
}
}
export async function POST(request: Request) {
try {
const formData = await request.formData();
const file = formData.get('file') as File;
if (!file) {
return NextResponse.json({ error: 'No file uploaded' }, { status: 400 });
}
// Mock AI analysis results
const mockAnalysis = {
fileName: file.name,
suggestedAltText: `Professional photograph showing ${file.name.replace(/\.[^/.]+$/, "").replace(/[-_]/g, ' ')} in a clear, well-lit environment`,
wcagScore: Math.floor(Math.random() * 20) + 80, // Random score between 80-100
improvements: [
'Add descriptive alt text for screen readers',
'Ensure image has sufficient color contrast',
'Consider adding captions for complex images',
'Verify image is meaningful and not decorative'
],
confidence: 0.95
};
return NextResponse.json(mockAnalysis);
} catch (error) {
console.error('Error processing accessibility audit:', error);
return NextResponse.json({ error: 'Failed to process audit' }, { status: 500 });
}
}
+143
View File
@@ -0,0 +1,143 @@
import { NextRequest, NextResponse } from 'next/server'
import { OpenAI } from 'openai'
import { PrismaClient } from '@prisma/client'
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
})
const prisma = new PrismaClient()
export async function POST(request: NextRequest) {
try {
const formData = await request.formData()
const file = formData.get('file') as File
const url = formData.get('url') as string
if (!file && !url) {
return NextResponse.json({ error: 'File or URL is required' }, { status: 400 })
}
let altText = ''
let wcagScore = 0
const issues: string[] = []
const suggestions: string[] = []
if (file) {
// Convert file to base64 for OpenAI Vision API
const bytes = await file.arrayBuffer()
const buffer = Buffer.from(bytes)
const base64 = buffer.toString('base64')
// Generate alt text using OpenAI Vision
const response = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
{
role: 'user',
content: [
{
type: 'text',
text: 'Generate concise, descriptive alt text for this image that would be useful for screen readers. Focus on the most important visual elements and context.',
},
{
type: 'image_url',
image_url: {
url: `data:${file.type};base64,${base64}`,
},
},
],
},
],
max_tokens: 100,
})
altText = response.choices[0]?.message?.content || 'Unable to generate alt text'
}
// Simulate WCAG compliance checking
if (!altText || altText.length < 10) {
issues.push('Missing or inadequate alt text')
suggestions.push('Add descriptive alt text for all images')
wcagScore = 0.3
} else if (altText.length > 125) {
issues.push('Alt text is too long')
suggestions.push('Keep alt text under 125 characters')
wcagScore = 0.7
} else {
wcagScore = 0.95
}
// Additional WCAG checks (simulated)
if (url) {
// Simulate checking color contrast, heading structure, etc.
const randomFactor = Math.random()
if (randomFactor < 0.3) {
issues.push('Low color contrast detected')
suggestions.push('Ensure color contrast ratio is at least 4.5:1')
wcagScore = Math.min(wcagScore, 0.6)
}
if (randomFactor < 0.2) {
issues.push('Missing heading structure')
suggestions.push('Use proper heading hierarchy (h1, h2, h3, etc.)')
wcagScore = Math.min(wcagScore, 0.5)
}
}
// Save audit to database
const audit = await prisma.accessibilityAudit.create({
data: {
url: url || 'uploaded-image',
imagePath: file ? file.name : null,
altText,
wcagScore,
issues: JSON.stringify(issues),
suggestions: JSON.stringify(suggestions),
},
})
return NextResponse.json({
success: true,
audit: {
id: audit.id,
altText,
wcagScore,
issues,
suggestions,
},
})
} catch (error) {
console.error('Error in accessibility API:', error)
return NextResponse.json(
{ error: 'Failed to process accessibility audit' },
{ status: 500 }
)
}
}
export async function GET() {
try {
const audits = await prisma.accessibilityAudit.findMany({
orderBy: {
createdAt: 'desc',
},
take: 50,
})
const avgScore = audits.length > 0
? audits.reduce((sum: number, audit: { wcagScore: number | null }) =>
sum + (audit.wcagScore || 0), 0) / audits.length
: 0
return NextResponse.json({
audits,
statistics: {
totalAudits: audits.length,
averageScore: Math.round(avgScore * 100) / 100,
},
})
} catch (error) {
console.error('Error fetching accessibility audits:', error)
return NextResponse.json({ error: 'Failed to fetch audits' }, { status: 500 })
}
}
+73
View File
@@ -0,0 +1,73 @@
import { NextResponse } from 'next/server';
// Mock data for demo purposes
const mockAudits = [
{
id: '1',
fileName: 'homepage-banner.jpg',
originalAltText: '',
suggestedAltText: 'University campus building with students walking in the foreground',
wcagScore: 85,
improvements: [
'Add descriptive alt text to improve accessibility',
'Ensure sufficient color contrast',
'Consider adding captions for better understanding'
],
createdAt: new Date('2024-12-01T10:00:00Z'),
userId: '1'
},
{
id: '2',
fileName: 'course-diagram.png',
originalAltText: 'diagram',
suggestedAltText: 'Flow chart showing course prerequisites with arrows connecting related subjects',
wcagScore: 92,
improvements: [
'Current alt text is good',
'Consider adding more descriptive details',
'Ensure text is readable at all zoom levels'
],
createdAt: new Date('2024-12-02T14:30:00Z'),
userId: '1'
}
];
export async function GET() {
try {
// Return mock data instead of database query
return NextResponse.json(mockAudits);
} catch (error) {
console.error('Error fetching accessibility audits:', error);
return NextResponse.json({ error: 'Failed to fetch audits' }, { status: 500 });
}
}
export async function POST(request: Request) {
try {
const formData = await request.formData();
const file = formData.get('file') as File;
if (!file) {
return NextResponse.json({ error: 'No file uploaded' }, { status: 400 });
}
// Mock AI analysis results
const mockAnalysis = {
fileName: file.name,
suggestedAltText: `Professional photograph showing ${file.name.replace(/\.[^/.]+$/, "").replace(/[-_]/g, ' ')} in a clear, well-lit environment`,
wcagScore: Math.floor(Math.random() * 20) + 80, // Random score between 80-100
improvements: [
'Add descriptive alt text for screen readers',
'Ensure image has sufficient color contrast',
'Consider adding captions for complex images',
'Verify image is meaningful and not decorative'
],
confidence: 0.95
};
return NextResponse.json(mockAnalysis);
} catch (error) {
console.error('Error processing accessibility audit:', error);
return NextResponse.json({ error: 'Failed to process audit' }, { status: 500 });
}
}
+253
View File
@@ -0,0 +1,253 @@
import { NextResponse } from 'next/server';
interface ApplicationData {
type: 'undergraduate' | 'postgraduate' | 'research' | 'international';
personalInfo: {
firstName: string;
lastName: string;
email: string;
phone: string;
dateOfBirth: string;
citizenship: string;
address: string;
};
academicInfo: {
previousEducation: string;
atar?: number;
transcripts: string[];
englishProficiency?: string;
};
coursePreferences: {
firstChoice: string;
secondChoice?: string;
thirdChoice?: string;
campus: string;
startDate: string;
};
documents: string[];
scholarshipInterest: boolean;
}
// Mock application database
const applications: Array<ApplicationData & { id: string; status: string; submittedAt: string }> = [];
export async function POST(request: Request) {
try {
const { action, ...data } = await request.json();
switch (action) {
case 'submit':
const applicationId = `APP-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const newApplication = {
id: applicationId,
...data as ApplicationData,
status: 'submitted',
submittedAt: new Date().toISOString()
};
applications.push(newApplication);
// Send confirmation email (mock)
console.log('Application submitted:', {
id: applicationId,
email: data.personalInfo?.email,
course: data.coursePreferences?.firstChoice
});
return NextResponse.json({
success: true,
applicationId,
message: 'Application submitted successfully',
nextSteps: [
'Check your email for confirmation',
'Upload required documents if not already provided',
'Monitor application status in your portal',
'Await assessment (typically 2-4 weeks)',
'Respond to offer if successful'
],
estimatedProcessingTime: '2-4 weeks',
contactInfo: {
phone: '+61 3 6226 6200',
email: 'admissions@utas.edu.au',
hours: 'Monday-Friday 9:00 AM - 5:00 PM'
}
});
case 'getStatus':
const { applicationId: statusId } = data;
const application = applications.find(app => app.id === statusId);
if (!application) {
return NextResponse.json({
error: 'Application not found'
}, { status: 404 });
}
return NextResponse.json({
application: {
id: application.id,
status: application.status,
submittedAt: application.submittedAt,
course: application.coursePreferences.firstChoice,
campus: application.coursePreferences.campus
},
timeline: [
{ step: 'Application Submitted', completed: true, date: application.submittedAt },
{ step: 'Document Verification', completed: false, estimated: '1-2 weeks' },
{ step: 'Academic Assessment', completed: false, estimated: '2-3 weeks' },
{ step: 'Offer Decision', completed: false, estimated: '3-4 weeks' },
{ step: 'Enrollment', completed: false, estimated: 'Upon acceptance' }
]
});
case 'getRequirements':
const { courseType, citizenship } = data;
const requirements = {
undergraduate: {
domestic: [
'Completed Year 12 or equivalent',
'ATAR score or alternative entry pathway',
'Prerequisite subjects for specific courses',
'English language proficiency',
'Valid identification documents'
],
international: [
'Completed secondary education equivalent to Australian Year 12',
'Academic transcripts (officially translated)',
'English proficiency (IELTS 6.0+ or equivalent)',
'Student visa documentation',
'Financial capacity evidence',
'Health insurance (OSHC)'
]
},
postgraduate: {
domestic: [
'Completed bachelor degree or equivalent',
'Academic transcripts',
'Work experience (for some programs)',
'Professional references',
'English language proficiency'
],
international: [
'Completed bachelor degree equivalent to Australian standard',
'Academic transcripts (officially translated)',
'English proficiency (IELTS 6.5+ or equivalent)',
'Student visa documentation',
'Financial capacity evidence',
'Health insurance (OSHC)',
'Professional experience (where required)'
]
}
};
const citizenshipType = citizenship === 'australian' || citizenship === 'permanent_resident'
? 'domestic' : 'international';
return NextResponse.json({
requirements: requirements[courseType as keyof typeof requirements]?.[citizenshipType] || [],
deadlines: {
semester1: {
domestic: 'December 31, 2024',
international: 'October 31, 2024'
},
semester2: {
domestic: 'May 31, 2025',
international: 'March 31, 2025'
}
},
fees: {
undergraduate: {
domestic: 'Commonwealth Supported Places available',
international: '$32,000 - $45,000 per year'
},
postgraduate: {
domestic: '$25,000 - $40,000 per year',
international: '$35,000 - $50,000 per year'
}
}
});
default:
return NextResponse.json({
error: 'Invalid action'
}, { status: 400 });
}
} catch (error) {
console.error('Application API error:', error);
return NextResponse.json({
error: 'Failed to process application request'
}, { status: 500 });
}
}
export async function GET(request: Request) {
try {
const { searchParams } = new URL(request.url);
const type = searchParams.get('type') || 'info';
if (type === 'info') {
return NextResponse.json({
applicationTypes: [
{
type: 'undergraduate',
title: 'Undergraduate Applications',
description: 'Bachelor degrees, diplomas, and certificates',
eligibility: 'Year 12 completion or equivalent',
portal: 'UAC UTAS portal'
},
{
type: 'postgraduate',
title: 'Postgraduate Applications',
description: 'Masters, graduate certificates and diplomas',
eligibility: 'Bachelor degree or equivalent + work experience',
portal: 'Direct UTAS application'
},
{
type: 'research',
title: 'Research Degrees',
description: 'PhD, Masters by Research',
eligibility: 'Honours degree or masters + research proposal',
portal: 'Research degree portal'
},
{
type: 'international',
title: 'International Applications',
description: 'For students requiring a student visa',
eligibility: 'Varies by course + English proficiency',
portal: 'International student portal'
}
],
support: {
phone: '+61 3 6226 6200',
email: 'admissions@utas.edu.au',
chat: 'Available 24/7 through this portal',
hours: 'Monday-Friday 9:00 AM - 5:00 PM AEST'
},
scholarships: {
available: true,
types: [
'Merit-based scholarships up to $5,000/year',
'Tasmanian scholarships up to $15,000/year',
'International student scholarships',
'Program-specific scholarships',
'Indigenous student support',
'Rural and regional scholarships'
],
deadline: 'Apply early for best scholarship opportunities'
}
});
}
return NextResponse.json({
error: 'Invalid request type'
}, { status: 400 });
} catch (error) {
console.error('Application info error:', error);
return NextResponse.json({
error: 'Failed to get application information'
}, { status: 500 });
}
}
+109
View File
@@ -0,0 +1,109 @@
import { NextResponse } from 'next/server';
// Mock chat responses
const mockResponses = [
{
trigger: ['library', 'hours', 'open'],
response: {
en: 'The library is open Monday-Friday 8:00 AM - 10:00 PM, Saturday 9:00 AM - 6:00 PM, and Sunday 12:00 PM - 8:00 PM. During exam periods, we have extended hours until midnight.',
ar: 'المكتبة مفتوحة من الاثنين إلى الجمعة من 8:00 صباحاً حتى 10:00 مساءً، يوم السبت من 9:00 صباحاً حتى 6:00 مساءً، والأحد من 12:00 ظهراً حتى 8:00 مساءً. خلال فترات الامتحانات، لدينا ساعات ممتدة حتى منتصف الليل.'
}
},
{
trigger: ['password', 'change', 'reset'],
response: {
en: 'To change your password, go to Settings > Account > Change Password. You can also reset it using the "Forgot Password" link on the login page.',
ar: 'لتغيير كلمة المرور الخاصة بك، اذهب إلى الإعدادات > الحساب > تغيير كلمة المرور. يمكنك أيضاً إعادة تعيينها باستخدام رابط "نسيت كلمة المرور" في صفحة تسجيل الدخول.'
}
},
{
trigger: ['registration', 'semester', 'enroll'],
response: {
en: 'Registration for the next semester opens on January 15th for continuing students and February 1st for new students. Please check your academic calendar for specific dates.',
ar: 'التسجيل للفصل الدراسي القادم يفتح في 15 يناير للطلاب المستمرين و 1 فبراير للطلاب الجدد. يرجى مراجعة التقويم الأكاديمي للتواريخ المحددة.'
}
},
{
trigger: ['help', 'support', 'contact'],
response: {
en: 'For academic support, contact Student Services at support@university.edu or call (555) 123-4567. For technical issues, email IT help desk at it@university.edu.',
ar: 'للدعم الأكاديمي، اتصل بخدمات الطلاب على support@university.edu أو اتصل بالرقم (555) 123-4567. للمشاكل التقنية، راسل مكتب المساعدة التقنية على it@university.edu.'
}
}
];
// Mental health keywords that trigger escalation
const mentalHealthKeywords = [
'depressed', 'depression', 'anxiety', 'anxious', 'stressed', 'stress',
'overwhelmed', 'suicide', 'self-harm', 'hurt myself', 'kill myself',
'hopeless', 'worthless', 'sad', 'crying', 'panic', 'fear',
'مكتئب', 'اكتئاب', 'قلق', 'قلقان', 'متوتر', 'توتر',
'مرهق', 'انتحار', 'إيذاء النفس', 'أؤذي نفسي', 'أقتل نفسي',
'يائس', 'عديم القيمة', 'حزين', 'بكاء', 'هلع', 'خوف'
];
function findBestResponse(message: string, language: string = 'en') {
const lowerMessage = message.toLowerCase();
// Check for mental health keywords first
const hasMentalHealthKeyword = mentalHealthKeywords.some(keyword =>
lowerMessage.includes(keyword.toLowerCase())
);
if (hasMentalHealthKeyword) {
return {
response: language === 'ar'
? 'أفهم أنك تمر بوقت صعب. من المهم أن تطلب المساعدة من المختصين. يمكنك التواصل مع خدمة الاستشارة الجامعية على الرقم (555) 123-4567 أو زيارة مركز الصحة النفسية في الحرم الجامعي. في حالات الطوارئ، اتصل بالرقم 911 أو خط المساعدة الوطني للأزمات النفسية.'
: 'I understand you\'re going through a difficult time. It\'s important to seek help from professionals. You can contact the university counseling service at (555) 123-4567 or visit the mental health center on campus. In emergencies, call 911 or the National Crisis Helpline.',
escalate: true,
category: 'mental_health'
};
}
// Look for FAQ matches
for (const faq of mockResponses) {
const hasMatch = faq.trigger.some(trigger =>
lowerMessage.includes(trigger.toLowerCase())
);
if (hasMatch) {
return {
response: faq.response[language as keyof typeof faq.response],
escalate: false,
category: 'faq'
};
}
}
// Default response
return {
response: language === 'ar'
? 'شكراً لك على سؤالك. يمكنني مساعدتك في العثور على المعلومات التي تحتاجها. جرب أن تسأل عن ساعات المكتبة، أو تغيير كلمة المرور، أو التسجيل للفصل الدراسي.'
: 'Thank you for your question. I can help you find the information you need. Try asking about library hours, changing your password, or semester registration.',
escalate: false,
category: 'general'
};
}
export async function POST(request: Request) {
try {
const body = await request.json();
const { message, language = 'en' } = body;
if (!message) {
return NextResponse.json({ error: 'Message is required' }, { status: 400 });
}
const result = findBestResponse(message, language);
return NextResponse.json({
response: result.response,
escalate: result.escalate,
category: result.category,
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('Error processing chat:', error);
return NextResponse.json({ error: 'Failed to process message' }, { status: 500 });
}
}
+61
View File
@@ -0,0 +1,61 @@
import { NextRequest, NextResponse } from 'next/server';
import UTASChatBot from '@/lib/chatbot';
// Initialize the chatbot (will use OpenRouter if API key is available)
const chatbot = new UTASChatBot();
export async function POST(request: NextRequest) {
try {
const { message, conversationHistory = [] } = await request.json();
if (!message || typeof message !== 'string') {
return NextResponse.json({
error: 'Message is required and must be a string'
}, { status: 400 });
}
// Generate AI response using RAG and potentially OpenRouter
const response = await chatbot.generateResponse(message, conversationHistory);
// Log the interaction for demo purposes
console.log('UTAS Chat:', {
timestamp: new Date().toISOString(),
message: message.substring(0, 100),
response: response.substring(0, 100)
});
return NextResponse.json({
message: response,
timestamp: new Date().toISOString(),
source: 'UTAS AI Assistant'
});
} catch (error) {
console.error('Chat API Error:', error);
return NextResponse.json({
message: "I apologize, but I'm experiencing technical difficulties. Please try again or contact UTAS directly at +61 3 6226 6200 or info@utas.edu.au for immediate assistance.",
timestamp: new Date().toISOString(),
source: 'UTAS AI Assistant',
error: true
}, { status: 500 });
}
}
export async function GET() {
return NextResponse.json({
service: 'UTAS AI Chat Assistant',
status: 'active',
features: [
'RAG-powered responses using UTAS knowledge base',
'OpenRouter AI integration (when API key provided)',
'Real-time course and program information',
'Application guidance and support',
'Campus and research information'
],
endpoints: {
POST: 'Send message and conversation history',
GET: 'Service status and information'
}
});
}
+109
View File
@@ -0,0 +1,109 @@
import { NextResponse } from 'next/server';
// Mock chat responses
const mockResponses = [
{
trigger: ['library', 'hours', 'open'],
response: {
en: 'The library is open Monday-Friday 8:00 AM - 10:00 PM, Saturday 9:00 AM - 6:00 PM, and Sunday 12:00 PM - 8:00 PM. During exam periods, we have extended hours until midnight.',
ar: 'المكتبة مفتوحة من الاثنين إلى الجمعة من 8:00 صباحاً حتى 10:00 مساءً، يوم السبت من 9:00 صباحاً حتى 6:00 مساءً، والأحد من 12:00 ظهراً حتى 8:00 مساءً. خلال فترات الامتحانات، لدينا ساعات ممتدة حتى منتصف الليل.'
}
},
{
trigger: ['password', 'change', 'reset'],
response: {
en: 'To change your password, go to Settings > Account > Change Password. You can also reset it using the "Forgot Password" link on the login page.',
ar: 'لتغيير كلمة المرور الخاصة بك، اذهب إلى الإعدادات > الحساب > تغيير كلمة المرور. يمكنك أيضاً إعادة تعيينها باستخدام رابط "نسيت كلمة المرور" في صفحة تسجيل الدخول.'
}
},
{
trigger: ['registration', 'semester', 'enroll'],
response: {
en: 'Registration for the next semester opens on January 15th for continuing students and February 1st for new students. Please check your academic calendar for specific dates.',
ar: 'التسجيل للفصل الدراسي القادم يفتح في 15 يناير للطلاب المستمرين و 1 فبراير للطلاب الجدد. يرجى مراجعة التقويم الأكاديمي للتواريخ المحددة.'
}
},
{
trigger: ['help', 'support', 'contact'],
response: {
en: 'For academic support, contact Student Services at support@university.edu or call (555) 123-4567. For technical issues, email IT help desk at it@university.edu.',
ar: 'للدعم الأكاديمي، اتصل بخدمات الطلاب على support@university.edu أو اتصل بالرقم (555) 123-4567. للمشاكل التقنية، راسل مكتب المساعدة التقنية على it@university.edu.'
}
}
];
// Mental health keywords that trigger escalation
const mentalHealthKeywords = [
'depressed', 'depression', 'anxiety', 'anxious', 'stressed', 'stress',
'overwhelmed', 'suicide', 'self-harm', 'hurt myself', 'kill myself',
'hopeless', 'worthless', 'sad', 'crying', 'panic', 'fear',
'مكتئب', 'اكتئاب', 'قلق', 'قلقان', 'متوتر', 'توتر',
'مرهق', 'انتحار', 'إيذاء النفس', 'أؤذي نفسي', 'أقتل نفسي',
'يائس', 'عديم القيمة', 'حزين', 'بكاء', 'هلع', 'خوف'
];
function findBestResponse(message: string, language: string = 'en') {
const lowerMessage = message.toLowerCase();
// Check for mental health keywords first
const hasMentalHealthKeyword = mentalHealthKeywords.some(keyword =>
lowerMessage.includes(keyword.toLowerCase())
);
if (hasMentalHealthKeyword) {
return {
response: language === 'ar'
? 'أفهم أنك تمر بوقت صعب. من المهم أن تطلب المساعدة من المختصين. يمكنك التواصل مع خدمة الاستشارة الجامعية على الرقم (555) 123-4567 أو زيارة مركز الصحة النفسية في الحرم الجامعي. في حالات الطوارئ، اتصل بالرقم 911 أو خط المساعدة الوطني للأزمات النفسية.'
: 'I understand you\'re going through a difficult time. It\'s important to seek help from professionals. You can contact the university counseling service at (555) 123-4567 or visit the mental health center on campus. In emergencies, call 911 or the National Crisis Helpline.',
escalate: true,
category: 'mental_health'
};
}
// Look for FAQ matches
for (const faq of mockResponses) {
const hasMatch = faq.trigger.some(trigger =>
lowerMessage.includes(trigger.toLowerCase())
);
if (hasMatch) {
return {
response: faq.response[language as keyof typeof faq.response],
escalate: false,
category: 'faq'
};
}
}
// Default response
return {
response: language === 'ar'
? 'شكراً لك على سؤالك. يمكنني مساعدتك في العثور على المعلومات التي تحتاجها. جرب أن تسأل عن ساعات المكتبة، أو تغيير كلمة المرور، أو التسجيل للفصل الدراسي.'
: 'Thank you for your question. I can help you find the information you need. Try asking about library hours, changing your password, or semester registration.',
escalate: false,
category: 'general'
};
}
export async function POST(request: Request) {
try {
const body = await request.json();
const { message, language = 'en' } = body;
if (!message) {
return NextResponse.json({ error: 'Message is required' }, { status: 400 });
}
const result = findBestResponse(message, language);
return NextResponse.json({
response: result.response,
escalate: result.escalate,
category: result.category,
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('Error processing chat:', error);
return NextResponse.json({ error: 'Failed to process message' }, { status: 500 });
}
}
+104
View File
@@ -0,0 +1,104 @@
import { NextRequest, NextResponse } from 'next/server'
import { OpenAI } from 'openai'
import { PrismaClient } from '@prisma/client'
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
})
const prisma = new PrismaClient()
type Message = {
sender: 'user' | 'bot'
text: string
timestamp: Date
}
export async function POST(request: NextRequest) {
try {
const { message, mode = 'general', history = [] } = await request.json()
// Get FAQs from database
const faqs = await prisma.fAQ.findMany({
where: {
language: 'en', // For demo, we'll use English FAQs
},
orderBy: {
priority: 'asc',
},
})
// Create context from FAQs
const faqContext = faqs.map((faq: { question: string; answer: string }) =>
`Q: ${faq.question}\nA: ${faq.answer}`
).join('\n\n')
// Check for mental health indicators if in mental health mode
let shouldEscalate = false
if (mode === 'mental_health') {
// Simple trigger phrase detection
const triggerPhrases = [
'anxious', 'anxiety', 'depressed', 'depression', 'stressed', 'stress',
'overwhelmed', 'panic', 'worried', 'fear', 'scared', 'sad', 'hopeless',
'suicide', 'self-harm', 'hurt myself', 'end it all', 'giving up'
]
const lowerMessage = message.toLowerCase()
shouldEscalate = triggerPhrases.some(phrase => lowerMessage.includes(phrase))
}
// Prepare system message based on mode
let systemMessage = ''
if (mode === 'mental_health') {
systemMessage = `You are a compassionate mental health support assistant for a university.
Provide empathetic, supportive responses. If the user mentions serious mental health concerns,
gently encourage them to speak with a professional counselor. Keep responses warm and understanding.`
} else {
systemMessage = `You are a helpful university assistant. Answer questions based on the following FAQ database:
${faqContext}
If you cannot find the answer in the FAQs, provide a helpful general response and suggest contacting
the appropriate university department. Keep responses concise and helpful.`
}
const completion = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
{ role: 'system', content: systemMessage },
...history.map((msg: Message) => ({
role: msg.sender === 'user' ? 'user' as const : 'assistant' as const,
content: msg.text,
})),
{ role: 'user', content: message },
],
max_tokens: 500,
temperature: 0.7,
})
const response = completion.choices[0]?.message?.content || 'I apologize, but I cannot provide a response at this time.'
// Log the chat session
await prisma.chatSession.create({
data: {
type: mode === 'mental_health' ? 'MENTAL_HEALTH' : 'GENERAL',
messages: JSON.stringify([
...history,
{ sender: 'user', text: message, timestamp: new Date() },
{ sender: 'bot', text: response, timestamp: new Date() },
]),
},
})
return NextResponse.json({
response,
shouldEscalate,
})
} catch (error) {
console.error('Error in chat API:', error)
return NextResponse.json(
{ error: 'Failed to process chat message' },
{ status: 500 }
)
}
}
+71
View File
@@ -0,0 +1,71 @@
import { NextRequest, NextResponse } from 'next/server';
import UTASChatBot from '@/lib/chatbot';
// Initialize the chatbot (will use OpenRouter if API key is available)
const chatbot = new UTASChatBot();
export async function POST(request: NextRequest) {
try {
const {
message,
conversationHistory = [],
systemPrompt = null,
language = 'en'
} = await request.json();
if (!message || typeof message !== 'string') {
return NextResponse.json({
error: 'Message is required and must be a string'
}, { status: 400 });
}
// Generate AI response using RAG and potentially OpenRouter with role-based context
const response = await chatbot.generateResponse(
message,
conversationHistory,
systemPrompt,
language
);
// Log the interaction for demo purposes
console.log('UTAS Chat:', {
timestamp: new Date().toISOString(),
message: message.substring(0, 100),
response: response.substring(0, 100)
});
return NextResponse.json({
message: response,
timestamp: new Date().toISOString(),
source: 'UTAS AI Assistant'
});
} catch (error) {
console.error('Chat API Error:', error);
return NextResponse.json({
message: "I apologize, but I'm experiencing technical difficulties. Please try again or contact UTAS directly at +61 3 6226 6200 or info@utas.edu.au for immediate assistance.",
timestamp: new Date().toISOString(),
source: 'UTAS AI Assistant',
error: true
}, { status: 500 });
}
}
export async function GET() {
return NextResponse.json({
service: 'UTAS AI Chat Assistant',
status: 'active',
features: [
'RAG-powered responses using UTAS knowledge base',
'OpenRouter AI integration (when API key provided)',
'Real-time course and program information',
'Application guidance and support',
'Campus and research information'
],
endpoints: {
POST: 'Send message and conversation history',
GET: 'Service status and information'
}
});
}
+127
View File
@@ -0,0 +1,127 @@
import { NextRequest, NextResponse } from 'next/server';
import { mockCourses, searchCourses, studyAreas } from '@/lib/mockData';
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const query = searchParams.get('q') || '';
const area = searchParams.get('area') || '';
const level = searchParams.get('level') || '';
const campus = searchParams.get('campus') || '';
// Convert level filter to studyMode
let studyMode = '';
if (level) {
if (level.toLowerCase() === 'undergraduate') {
studyMode = 'undergraduate';
} else if (level.toLowerCase() === 'postgraduate') {
studyMode = 'postgraduate';
} else if (level.toLowerCase() === 'research') {
studyMode = 'research';
}
}
// Build filters object
const filters: { area?: string; studyMode?: string; availability?: string } = {};
if (area) filters.area = area;
if (studyMode) filters.studyMode = studyMode;
let results = searchCourses(query, filters);
// Filter by campus if specified
if (campus) {
results = results.filter(course =>
course.campus.some(c => c.toLowerCase() === campus.toLowerCase())
);
}
return NextResponse.json({
success: true,
courses: results,
total: results.length,
filters: {
query,
area,
level,
campus
},
areas: studyAreas.map(area => area.name),
campuses: ["Hobart", "Launceston", "Burnie", "Sydney"]
});
} catch (error) {
console.error('Courses API error:', error);
return NextResponse.json(
{
success: false,
error: 'Failed to fetch courses',
courses: mockCourses.slice(0, 5), // Return some courses as fallback
total: 5
},
{ status: 500 }
);
}
}
export async function POST(request: NextRequest) {
try {
const { courseId, action } = await request.json();
if (action === 'getDetails') {
const course = mockCourses.find(c => c.id === courseId);
if (!course) {
return NextResponse.json({
error: 'Course not found'
}, { status: 404 });
}
return NextResponse.json({
success: true,
course: course,
relatedCourses: mockCourses
.filter(c => c.area === course.area && c.id !== courseId)
.slice(0, 3),
applicationInfo: {
process: course.area.includes('Medicine')
? 'Competitive entry with UCAT and interview required'
: 'Standard application through UAC UTAS portal',
requirements: course.entry,
deadlines: {
semester1: 'December 31, 2024',
semester2: 'May 31, 2025'
},
scholarships: [
'UTAS Merit Scholarship - $5,000/year',
'Tasmanian Scholarship - $15,000/year (for mainland students)',
`${course.area} specific scholarships available`
]
}
});
}
if (action === 'apply') {
// Mock application process
return NextResponse.json({
success: true,
message: 'Application submitted successfully',
applicationId: `APP-${Date.now()}`,
nextSteps: [
'Check your email for confirmation',
'Complete required documents',
'Attend orientation session'
]
});
}
return NextResponse.json({
error: 'Invalid action'
}, { status: 400 });
} catch (error) {
console.error('Course action error:', error);
return NextResponse.json({
error: 'Failed to process request'
}, { status: 500 });
}
}
+58
View File
@@ -0,0 +1,58 @@
import { NextResponse } from 'next/server';
// Mock survey data
const mockSurveys = [
{
id: '1',
rating: 5,
feedback: 'Great chatbot experience!',
category: 'chatbot',
createdAt: new Date('2024-12-01T10:00:00Z'),
userId: '1'
},
{
id: '2',
rating: 4,
feedback: 'Dashboard is very helpful',
category: 'dashboard',
createdAt: new Date('2024-12-02T14:30:00Z'),
userId: '1'
}
];
export async function GET() {
try {
return NextResponse.json(mockSurveys);
} catch (error) {
console.error('Error fetching surveys:', error);
return NextResponse.json({ error: 'Failed to fetch surveys' }, { status: 500 });
}
}
export async function POST(request: Request) {
try {
const body = await request.json();
const { rating, feedback, category } = body;
if (!rating || !feedback || !category) {
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
}
// Mock survey creation
const newSurvey = {
id: Date.now().toString(),
rating,
feedback,
category,
createdAt: new Date(),
userId: '1'
};
mockSurveys.push(newSurvey);
return NextResponse.json(newSurvey);
} catch (error) {
console.error('Error creating survey:', error);
return NextResponse.json({ error: 'Failed to create survey' }, { status: 500 });
}
}
+58
View File
@@ -0,0 +1,58 @@
import { NextRequest, NextResponse } from 'next/server'
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
export async function POST(request: NextRequest) {
try {
const { rating, type, feedback, sessionId, userId } = await request.json()
const survey = await prisma.survey.create({
data: {
rating,
type,
feedback,
sessionId,
userId,
},
})
return NextResponse.json({ success: true, survey })
} catch (error) {
console.error('Error creating survey:', error)
return NextResponse.json({ error: 'Failed to submit survey' }, { status: 500 })
}
}
export async function GET() {
try {
const surveys = await prisma.survey.findMany({
orderBy: {
createdAt: 'desc',
},
take: 100,
})
// Calculate statistics
const totalSurveys = surveys.length
const averageRating = totalSurveys > 0
? surveys.reduce((sum: number, survey: { rating: number }) => sum + survey.rating, 0) / totalSurveys
: 0
const ratingDistribution = surveys.reduce((acc: Record<number, number>, survey: { rating: number }) => {
acc[survey.rating] = (acc[survey.rating] || 0) + 1
return acc
}, {} as Record<number, number>)
return NextResponse.json({
surveys,
statistics: {
totalSurveys,
averageRating: Math.round(averageRating * 10) / 10,
ratingDistribution,
},
})
} catch (error) {
console.error('Error fetching surveys:', error)
return NextResponse.json({ error: 'Failed to fetch surveys' }, { status: 500 })
}
}
+58
View File
@@ -0,0 +1,58 @@
import { NextResponse } from 'next/server';
// Mock survey data
const mockSurveys = [
{
id: '1',
rating: 5,
feedback: 'Great chatbot experience!',
category: 'chatbot',
createdAt: new Date('2024-12-01T10:00:00Z'),
userId: '1'
},
{
id: '2',
rating: 4,
feedback: 'Dashboard is very helpful',
category: 'dashboard',
createdAt: new Date('2024-12-02T14:30:00Z'),
userId: '1'
}
];
export async function GET() {
try {
return NextResponse.json(mockSurveys);
} catch (error) {
console.error('Error fetching surveys:', error);
return NextResponse.json({ error: 'Failed to fetch surveys' }, { status: 500 });
}
}
export async function POST(request: Request) {
try {
const body = await request.json();
const { rating, feedback, category } = body;
if (!rating || !feedback || !category) {
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
}
// Mock survey creation
const newSurvey = {
id: Date.now().toString(),
rating,
feedback,
category,
createdAt: new Date(),
userId: '1'
};
mockSurveys.push(newSurvey);
return NextResponse.json(newSurvey);
} catch (error) {
console.error('Error creating survey:', error);
return NextResponse.json({ error: 'Failed to create survey' }, { status: 500 });
}
}
+52
View File
@@ -0,0 +1,52 @@
import { NextResponse } from 'next/server';
// Mock user profile data
const mockUserProfile = {
id: '1',
email: 'student@university.edu',
name: 'John Doe',
role: 'STUDENT',
studentId: 'ST001234',
faculty: 'Arts',
balance: 5420.50,
enrollments: [
{
id: '1',
course: {
id: '1',
code: 'CS101',
title: 'Introduction to Computer Science',
credits: 3,
instructor: 'Dr. Smith',
schedule: 'MWF 10:00-11:00'
},
grade: 'A',
semester: 'Fall 2024'
},
{
id: '2',
course: {
id: '2',
code: 'MATH201',
title: 'Calculus II',
credits: 4,
instructor: 'Prof. Johnson',
schedule: 'TTh 2:00-3:30'
},
grade: 'B+',
semester: 'Fall 2024'
}
],
createdAt: new Date('2024-09-01T00:00:00Z'),
updatedAt: new Date('2024-12-01T00:00:00Z')
};
export async function GET() {
try {
// Return mock user profile
return NextResponse.json(mockUserProfile);
} catch (error) {
console.error('Error fetching user profile:', error);
return NextResponse.json({ error: 'Failed to fetch user profile' }, { status: 500 });
}
}
@@ -0,0 +1,36 @@
import { NextRequest, NextResponse } from 'next/server'
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)
const email = searchParams.get('email')
if (!email) {
return NextResponse.json({ error: 'Email is required' }, { status: 400 })
}
const user = await prisma.user.findUnique({
where: { email },
include: {
enrollments: {
include: {
course: true,
},
},
advisor: true,
},
})
if (!user) {
return NextResponse.json({ error: 'User not found' }, { status: 404 })
}
return NextResponse.json(user)
} catch (error) {
console.error('Error fetching user profile:', error)
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}
+52
View File
@@ -0,0 +1,52 @@
import { NextResponse } from 'next/server';
// Mock user profile data
const mockUserProfile = {
id: '1',
email: 'student@university.edu',
name: 'John Doe',
role: 'STUDENT',
studentId: 'ST001234',
faculty: 'Arts',
balance: 5420.50,
enrollments: [
{
id: '1',
course: {
id: '1',
code: 'CS101',
title: 'Introduction to Computer Science',
credits: 3,
instructor: 'Dr. Smith',
schedule: 'MWF 10:00-11:00'
},
grade: 'A',
semester: 'Fall 2024'
},
{
id: '2',
course: {
id: '2',
code: 'MATH201',
title: 'Calculus II',
credits: 4,
instructor: 'Prof. Johnson',
schedule: 'TTh 2:00-3:30'
},
grade: 'B+',
semester: 'Fall 2024'
}
],
createdAt: new Date('2024-09-01T00:00:00Z'),
updatedAt: new Date('2024-12-01T00:00:00Z')
};
export async function GET() {
try {
// Return mock user profile
return NextResponse.json(mockUserProfile);
} catch (error) {
console.error('Error fetching user profile:', error);
return NextResponse.json({ error: 'Failed to fetch user profile' }, { status: 500 });
}
}