Skip to content
Merged

Maaz #45

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ router.use((err, req, res, next) => {
res.render('templates/Error/500', { error: err });
});

const port = 3130;
const port = 3030;

// Start the router
router.listen(port, () => {
Expand Down
341 changes: 235 additions & 106 deletions routes/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,25 +54,28 @@ async function fetchChatHistories(username) {
const now = new Date();
const diffInSeconds = Math.floor((now - createdAt) / 1000);

let formattedTime;
if (diffInSeconds < 60) {
formattedTime = `${diffInSeconds} seconds ago`;
} else if (diffInSeconds < 3600) {
const minutes = Math.floor(diffInSeconds / 60);
formattedTime = `${minutes} minute${minutes > 1 ? 's' : ''} ago`;
} else if (diffInSeconds < 86400) {
const hours = Math.floor(diffInSeconds / 3600);
formattedTime = `${hours} hour${hours > 1 ? 's' : ''} ago`;
} else {
const days = Math.floor(diffInSeconds / 86400);
formattedTime = `${days} day${days > 1 ? 's' : ''} ago`;
}
let formattedTime;
if (diffInSeconds < 60) {
formattedTime = `${diffInSeconds} second${diffInSeconds > 1 ? 's' : ''} ago`;
} else if (diffInSeconds < 3600) {
const minutes = Math.floor(diffInSeconds / 60);
formattedTime = `${minutes} minute${minutes > 1 ? 's' : ''} ago`;
} else if (diffInSeconds < 86400) {
const hours = Math.floor(diffInSeconds / 3600);
formattedTime = `${hours} hour${hours > 1 ? 's' : ''} ago`;
} else if (diffInSeconds < 2592000) { // Less than 30 days
const days = Math.floor(diffInSeconds / 86400);
formattedTime = `${days} day${days > 1 ? 's' : ''} ago`;
} else {
// Format as date if older than 30 days
formattedTime = createdAt.toLocaleDateString(); // Local date format
}

return {
...row,
created_at: formattedTime,
temperature: row.temperature || 0.5 // Default if null
};
return {
...row,
created_at: formattedTime,
temperature: row.temperature || 0.5 // Default if null
};
});
} catch (error) {
console.error("Error fetching chat histories:", error);
Expand Down Expand Up @@ -253,111 +256,237 @@ async function mallow(prompt) {
return aiResponseText;
}
}
async function NVIDIA(apiKey, modelName, conversationHistory, temperature) {
// modelName should be the *full* name (e.g., "nvidia/llama-3.1...")
const nvidiaApiUrl = "https://integrate.api.nvidia.com/v1/chat/completions";
const defaultMaxTokens = 4096;
const defaultTopP = 1.0; // Some models prefer 1.0 for top_p with temperature control

// Transform history to NVIDIA format {role: 'user'/'assistant', content: '...'}
const nvidiaFormattedHistory = conversationHistory.map(message => ({
role: message.role === 'model' ? 'assistant' : message.role, // map 'model' to 'assistant'
content: message.parts?.[0]?.text || '' // Extract text
})).filter(msg => msg.content); // Filter out messages with no content

router.post('/api/bot-chat', checkMessageLimit, async (req, res) => {
const userMessage = req.body.message;
if (!userMessage) {
return res.status(400).json({ message: "Chat message is required." });
}
const username = req.session.user.username; // Assuming you have user session

const userSettings = await getUserSettings(username);
const temperature = Math.min(Math.max(parseFloat(userSettings.temperature || 1.0), 0), 2);
let conversationHistory = [];
const chatId = req.body.chatId;

try { // Wrap the chat history fetching in try-catch
if (chatId) {
const fetchedHistory = await fetchChatHistoryById(chatId);
if (fetchedHistory && fetchedHistory.conversation_history) {
conversationHistory = fetchedHistory.conversation_history;
} else if (chatId && !fetchedHistory) {
// Handle case where chatId is provided but no history found (optional, depends on desired behavior)
return res.status(404).json({ message: "Chat history not found for the given chatId." });
}
}
} catch (dbError) { // Catch errors from fetchChatHistoryById (database errors)
console.error("Error fetching chat history:", dbError);
return res.status(500).json({ message: "Failed to fetch chat history.", error: dbError.message });
}
try {
const nvidiaResponse = await fetch(nvidiaApiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
//'NVCF-INPUT-MODE': 'TEXT' // May be needed for some models/endpoints
},
body: JSON.stringify({
model: modelName, // Use the full model name (e.g., "nvidia/llama...")
messages: nvidiaFormattedHistory,
temperature: temperature,
top_p: defaultTopP,
max_tokens: defaultMaxTokens,
stream: false
})
});

const responseBody = await nvidiaResponse.text(); // Read body once

if (!nvidiaResponse.ok) {
let errorData = {};
try {
errorData = JSON.parse(responseBody);
} catch(e) {
errorData = { error: { message: responseBody } };
}
console.error("NVIDIA API error response:", errorData);
console.error("NVIDIA API error status:", nvidiaResponse.status); // Log status too
// Extract detail if available (NVIDIA sometimes uses 'detail')
const errorDetail = errorData.detail || errorData.error?.message;
throw new Error(`NVIDIA API request failed (${nvidiaResponse.status}): ${errorDetail || 'Unknown NVIDIA error'}`);
}

conversationHistory.push({ role: "user", parts: [{ text: userMessage }] });
const nvidiaData = JSON.parse(responseBody);
const aiResponseText = nvidiaData.choices?.[0]?.message?.content;
if (typeof aiResponseText !== 'string') {
console.error("NVIDIA API Response missing expected content:", nvidiaData);
throw new Error("NVIDIA API returned a response without the expected text content.");
}
return aiResponseText;

let aiResponseText;
const models_name = userSettings.modelName; //"gemini-1.5-flash" "gemini-2.0-flash" "deepseek-chat" or "mallow"
console.log("Model Name:", models_name);
} catch (error) {
console.error("Error calling NVIDIA API:", error);
throw error;
}
}

router.post('/api/bot-chat', checkMessageLimit, async (req, res) => {
const userMessage = req.body.message;
const username = req.session.user.username; // Assuming you have user session
const chatId = req.body.chatId;

if (models_name === "deepseek-chat") {
// ... Deepseek API code (as you provided) ...
let deepseekApiKey;
deepseekApiKey = process.env.Deepseek_maaz_waheed; // Assuming you have DEEPSEEK_API_KEY in your env
if (!deepseekApiKey) {
return res.status(500).json({ error: "Deepseek API key not configured." });
}
let userSettings;
try {
const deepseekFormattedHistory = transformGeminiToDeepseekHistory(conversationHistory);
aiResponseText = await Deepseek(deepseekApiKey, models_name, deepseekFormattedHistory, temperature);
} catch (deepseekError) {
return res.status(500).json({ message: "Error processing Deepseek API.", error: deepseekError.message });
userSettings = await getUserSettings(username);
if (!userSettings || !userSettings.modelName) {
console.error(`User settings or modelName not found for user: ${username}`);
return res.status(500).json({ message: "Could not retrieve user settings or model configuration." });
}
} catch (settingsError) {
console.error("Error fetching user settings:", settingsError);
return res.status(500).json({ message: "Failed to fetch user settings.", error: settingsError.message });
}

} else if (models_name === "mallow-t1") { // Add condition for mallow
try {
aiResponseText = await mallow(userMessage); // Call mallow function with user message
} catch (mallowError) {
return res.status(500).json({ message: "Error processing Mallow API.", error: mallowError.message });
const temperature = Math.min(Math.max(parseFloat(userSettings.temperature || 1.0), 0), 2);
let conversationHistory = [];

try { // Wrap the chat history fetching in try-catch
if (chatId) {
const fetchedHistory = await fetchChatHistoryById(chatId);
if (fetchedHistory && fetchedHistory.conversation_history) {
// Ensure history is parsed if stored as JSON string
conversationHistory = typeof fetchedHistory.conversation_history === 'string'
? JSON.parse(fetchedHistory.conversation_history)
: fetchedHistory.conversation_history;
} else if (chatId && !fetchedHistory) {
return res.status(404).json({ message: "Chat history not found for the given chatId." });
}
}
} catch (dbError) { // Catch errors from fetchChatHistoryById (database errors)
console.error("Error fetching chat history:", dbError);
return res.status(500).json({ message: "Failed to fetch chat history.", error: dbError.message });
}

} else { // Default to Gemini if modelName is not deepseek-chat or mallow (or any other model you might add)
let geminiApiKey;
geminiApiKey = process.env.GEMINI_API_KEY_maaz_waheed;
// Add the new user message to the history (Gemini format is the base)
conversationHistory.push({ role: "user", parts: [{ text: userMessage }] });

if (!geminiApiKey) {
return res.status(500).json({ error: "Gemini API key not configured." });
}
try {
aiResponseText = await gemini(geminiApiKey, models_name, conversationHistory, temperature);
} catch (geminiError) {
return res.status(500).json({ message: "Error processing Gemini API.", error: geminiError.message });
}
}
let aiResponseText;
const fullModelName = userSettings.modelName; // e.g., "gemini/gemini-1.5-flash", "nvidia/llama-3.1..."
console.log("Using full model name from settings:", fullModelName);

if (aiResponseText) {
if (models_name !== "mallow-t1") { // Only save history if not mallow
conversationHistory.push({ role: "model", parts: [{ text: aiResponseText }] });
let provider = '';
let modelIdentifier = '';

try {
if (chatId) {
await pool.query(
`UPDATE Ai_history
SET conversation_history = $1,
created_at = CURRENT_TIMESTAMP,
temperature = $3
WHERE id = $2`,
[JSON.stringify(conversationHistory), chatId, temperature]
);
} else {
const insertResult = await pool.query(
`INSERT INTO Ai_history (conversation_history, username, temperature)
VALUES ($1, $2, $3)
RETURNING id`,
[JSON.stringify(conversationHistory), req.session.user.username, temperature]
);
req.newChatId = insertResult.rows[0].id;
if (fullModelName.includes('/')) {
const parts = fullModelName.split('/');
provider = parts[0].toLowerCase(); // gemini, deepseek, nvidia, mallow
modelIdentifier = parts.slice(1).join('/'); // Handles cases like nvidia/something/else if needed, but usually just parts[1]
} else {
// Handle cases where there might be no slash (legacy or direct name)
// You might want to default to a provider or throw an error
console.warn(`Model name "${fullModelName}" does not contain a provider prefix. Attempting to handle directly or defaulting.`);
// For now, let's try matching known non-prefixed names or default later
modelIdentifier = fullModelName;
// You could try to infer the provider here based on the name if needed
if (modelIdentifier === 'deepseek-chat') provider = 'deepseek';
else if (modelIdentifier === 'mallow-t1') provider = 'mallow';
else provider = 'gemini'; // Default assumption if no prefix and not known name
}

console.log(`Provider: ${provider}, Model Identifier: ${modelIdentifier}`);

try { // Wrap all API calls in a single try-catch
switch (provider) {
case 'deepseek':
/* console.log(`Calling Deepseek API with model: ${modelIdentifier}`);
const deepseekApiKey = process.env.Deepseek_maaz_waheed; // Or your specific key variable
if (!deepseekApiKey) {
throw new Error("Deepseek API key not configured in environment variables.");
}
// Deepseek needs a different history format
const deepseekFormattedHistory = transformGeminiToDeepseekHistory(conversationHistory);
aiResponseText ="it is not available." //await Deepseek(deepseekApiKey, modelIdentifier, deepseekFormattedHistory, temperature);*/
aiResponseText ="it is not available. deepseek-v3 change model"
break;

case 'mallow':
console.log(`Calling Mallow API (model identifier "${modelIdentifier}" not used by function)`);
// Mallow function currently only takes the prompt (userMessage)
aiResponseText = await mallow(userMessage);
break;

case 'nvidia':
// NVIDIA API expects the *full* model name including the prefix
const nvidiaModelName = fullModelName; // Use the original full name
console.log(`Calling NVIDIA API with model: ${nvidiaModelName}`);
const nvidiaApiKey = process.env.NVIDIA_API; // MAKE SURE YOU HAVE THIS ENV VAR SET
if (!nvidiaApiKey) {
throw new Error("NVIDIA API key (NVIDIA_API_KEY) not configured in environment variables.");
}
// The NVIDIA function handles history transformation internally
aiResponseText = await NVIDIA(nvidiaApiKey, nvidiaModelName, conversationHistory, temperature);
break;
case 'gemini':
default: // Default to Gemini if provider is 'gemini' or unknown/unhandled
if (provider !== 'gemini') {
console.warn(`Unknown or unhandled provider "${provider}". Defaulting to Gemini.`);
}
console.log(`Calling Gemini API with model: ${modelIdentifier}`);
const geminiApiKey = process.env.GEMINI_API_KEY_maaz_waheed; // Or your specific key variable
if (!geminiApiKey) {
throw new Error("Gemini API key not configured in environment variables.");
}
aiResponseText = await gemini(geminiApiKey, modelIdentifier, conversationHistory, temperature);
break;
}
} catch (dbError) {
console.error("Error updating/inserting chat history:", dbError);
return res.status(500).json({ message: "Failed to save chat history.", error: dbError.message });
}
} // End of history saving condition

res.json({ aiResponse: aiResponseText, newChatId: req.newChatId }); // newChatId might be undefined for mallow, which is fine if you don't need it
} else {
res.status(500).json({ message: "AI API returned empty response." });
}
} catch (apiError) {
console.error(`Error calling AI API for provider ${provider} with model ${modelIdentifier}:`, apiError);
// Send back the specific error message from the API call if available
return res.status(500).json({ message: `Error processing ${provider} API request.`, error: apiError.message });
}

// --- Process response and save history ---
if (aiResponseText !== undefined && aiResponseText !== null) { // Check if response is valid
let newChatId = chatId; // Keep existing chatId unless a new one is created

// Only save history if the provider wasn't Mallow (as per original logic)
// Or add specific conditions for other models if needed
if (provider !== 'mallow' && provider !== 'deepseek') {
// Add AI response to history (Gemini format)
conversationHistory.push({ role: "model", parts: [{ text: aiResponseText }] });

try {
const historyJson = JSON.stringify(conversationHistory); // Ensure it's stringified for DB

if (chatId) { // Update existing history
await pool.query(
`UPDATE Ai_history
SET conversation_history = $1,
created_at = CURRENT_TIMESTAMP,
temperature = $3
WHERE id = $2`,
[historyJson, chatId, temperature]
);
} else { // Insert new history record
const insertResult = await pool.query(
`INSERT INTO Ai_history (conversation_history, username, temperature)
VALUES ($1, $2, $3)
RETURNING id`,
[historyJson, username, temperature]
);
newChatId = insertResult.rows[0].id; // Get the ID of the newly created chat
req.newChatId = newChatId; // Assign to req object if needed elsewhere (like logging)
}
} catch (dbError) {
console.error("Error updating/inserting chat history:", dbError);
// Decide if you want to return the AI response even if saving failed
// For now, let's return an error indicating save failure
return res.status(500).json({
message: "AI response generated, but failed to save chat history.",
error: dbError.message,
aiResponse: aiResponseText // Optionally include the response anyway
});
}
} // End of history saving condition

// Send successful response
res.json({ aiResponse: aiResponseText, newChatId: newChatId }); // Return newChatId consistently

} else {
// Handle cases where the API might have technically succeeded (no error thrown) but returned no text
console.error(`AI API for provider ${provider} returned an empty or invalid response.`);
res.status(500).json({ message: `AI API (${provider}) returned an empty response.` });
}
});


router.post('/api/chat/clear-history/:chatId', async (req, res) => {
const chatId = req.params.chatId;
if (!chatId) {
Expand Down
Loading