-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfunctions.php
More file actions
443 lines (373 loc) · 15.1 KB
/
Copy pathfunctions.php
File metadata and controls
443 lines (373 loc) · 15.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
<?php
// Helper functions for the application
/**
* Make API call to the Gemini AI model
*
* @param string $systemPrompt The system prompt defining the AI personality
* @param array $messages The conversation history
* @return string The AI response
*/
// Function to get AI response using Gemini API
function getAIResponse(array $messages, string $systemPrompt): string {
global $conn; // Make sure $conn is available if needed elsewhere, though not directly used here
// Check if API constants are defined
if (!defined('GEMINI_API_KEY') || !defined('GEMINI_API_URL_BASE')) {
error_log("Error: Gemini API Key or URL Base not defined in config.php.");
return "Error: API configuration is missing.";
}
$apiKey = GEMINI_API_KEY;
$apiUrl = GEMINI_API_URL_BASE . '?key=' . $apiKey;
// Format messages for Gemini API ('user' and 'model' roles)
$contents = [];
$lastRole = ''; // Track the last role added to enforce alternation
foreach ($messages as $msg) {
$currentRole = null;
// Validate and map roles explicitly
if ($msg['role'] === 'user') {
$currentRole = 'user';
} elseif ($msg['role'] === 'assistant') {
$currentRole = 'model';
} else {
// Log and skip messages with invalid roles from the database/input
error_log("Invalid role ('" . $msg['role'] . "') found in message history. Skipping message ID: " . ($msg['id'] ?? 'N/A'));
continue;
}
// Prevent sending consecutive messages with the same role
if (!empty($lastRole) && $currentRole === $lastRole) {
error_log("Skipping consecutive message with role '$currentRole'. Message ID: " . ($msg['id'] ?? 'N/A'));
continue; // Skip this message to maintain role alternation
}
// Add the valid, alternating message to the contents array
$contents[] = ['role' => $currentRole, 'parts' => [['text' => $msg['content']]]];
$lastRole = $currentRole; // Update the last role added
}
// Ensure the conversation history is not empty and ideally ends with a 'user' role
if (empty($contents)) {
error_log("Warning: Attempting API call with empty message history after filtering.");
// Decide how to handle this: return error, default message?
// return "Error: No valid messages to send."; // Option: return error
} elseif (end($contents)['role'] !== 'user') {
// This usually means the last message fetched was from the assistant.
// The API needs a user prompt to respond to.
error_log("Warning: Conversation history does not end with a 'user' role. Last role: " . end($contents)['role']);
// This might lead to API errors depending on the model's requirements.
// Consider if chat.php should always ensure the final message is the user's input.
}
// Prepare the data payload for the API
$data = [
'contents' => $contents,
// Optional: Add generation configuration
'generationConfig' => [
'temperature' => 0.7, // Example: Adjust creativity
'maxOutputTokens' => 1500, // Example: Limit response length
]
];
// Use system_instruction for the system prompt (supported by Gemini 1.5+)
if (!empty($systemPrompt)) {
$data['system_instruction'] = [
'parts' => [['text' => $systemPrompt]]
];
}
$payload = json_encode($data);
// Check for JSON encoding errors before sending
if (json_last_error() !== JSON_ERROR_NONE) {
error_log("JSON Encode Error: " . json_last_error_msg() . " - Data: " . print_r($data, true));
return "Error: Failed to prepare data for AI service.";
}
// Initialize cURL session
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $apiUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_TIMEOUT, 60); // Add a timeout (e.g., 60 seconds)
// Execute cURL request
$response = curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
// Handle cURL errors
if ($curlError) {
error_log("cURL Error calling Gemini API: " . $curlError);
return "Error: Could not connect to the AI service (cURL error).";
}
// Handle HTTP errors from the API
if ($httpcode >= 400) {
// Log detailed error information for debugging
error_log("Gemini API Error: HTTP Status $httpcode. Payload: $payload. Response: $response");
// Provide a user-friendly error message, including the response if possible
return "Error: API request failed with status code $httpcode. Response: " . htmlspecialchars($response);
}
// Decode the successful JSON response
$result = json_decode($response, true);
// Extract the AI's response text
if (isset($result['candidates'][0]['content']['parts'][0]['text'])) {
return $result['candidates'][0]['content']['parts'][0]['text'];
} elseif (isset($result['error'])) {
// Handle cases where the response is 200 OK but contains an error object
error_log("Gemini API Error (in 200 OK response): " . json_encode($result['error']));
return "Error from AI service: " . htmlspecialchars($result['error']['message'] ?? 'Unknown error structure');
} else {
// Log unexpected response structure
error_log("Unexpected Gemini API response structure. Payload: $payload. Response: " . $response);
return "Error: Received an unexpected response format from the AI service.";
}
}
/**
* Register a new user
*
* @param string $username The username
* @param string $email The email address
* @param string $password The password
* @return array Status and message
*/
function registerUser($username, $email, $password) {
global $conn;
// Validate input
if (empty($username) || empty($email) || empty($password)) {
return ['success' => false, 'message' => 'All fields are required'];
}
// Check if username or email already exists
$stmt = $conn->prepare("SELECT id FROM users WHERE username = ? OR email = ?");
$stmt->bind_param("ss", $username, $email);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows > 0) {
return ['success' => false, 'message' => 'Username or email already exists'];
}
// Hash password
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
// Insert new user
$stmt = $conn->prepare("INSERT INTO users (username, email, password) VALUES (?, ?, ?)");
$stmt->bind_param("sss", $username, $email, $hashedPassword);
if ($stmt->execute()) {
return ['success' => true, 'message' => 'Registration successful'];
} else {
return ['success' => false, 'message' => 'Registration failed: ' . $conn->error];
}
}
/**
* Authenticate user
*
* @param string $username The username or email
* @param string $password The password
* @return array Status and user data if successful
*/
function loginUser($username, $password) {
global $conn;
// Validate input
if (empty($username) || empty($password)) {
return ['success' => false, 'message' => 'All fields are required'];
}
// Check if input is email or username
$field = filter_var($username, FILTER_VALIDATE_EMAIL) ? 'email' : 'username';
// Get user
$stmt = $conn->prepare("SELECT id, username, password FROM users WHERE $field = ?");
$stmt->bind_param("s", $username);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
return ['success' => false, 'message' => 'Invalid username or password'];
}
$user = $result->fetch_assoc();
// Verify password
if (password_verify($password, $user['password'])) {
// Remove password from user data
unset($user['password']);
return ['success' => true, 'user' => $user];
} else {
return ['success' => false, 'message' => 'Invalid username or password'];
}
}
/**
* Create a new AI personality
*
* @param int $userId The user ID
* @param string $name The personality name
* @param string $description The personality description
* @param string $systemPrompt The system prompt
* @param string $avatar The avatar image path
* @return array Status and personality ID if successful
*/
function createPersonality($userId, $name, $description, $systemPrompt, $avatar = 'default.png') {
global $conn;
// Validate input
if (empty($name) || empty($systemPrompt)) {
return ['success' => false, 'message' => 'Name and system prompt are required'];
}
// Insert new personality
$stmt = $conn->prepare("INSERT INTO personalities (user_id, name, description, system_prompt, avatar) VALUES (?, ?, ?, ?, ?)");
$stmt->bind_param("issss", $userId, $name, $description, $systemPrompt, $avatar);
if ($stmt->execute()) {
$personalityId = $conn->insert_id;
return ['success' => true, 'personality_id' => $personalityId];
} else {
return ['success' => false, 'message' => 'Failed to create personality: ' . $conn->error];
}
}
/**
* Get all personalities for a user
*
* @param int $userId The user ID
* @return array List of personalities
*/
function getUserPersonalities($userId) {
global $conn;
$stmt = $conn->prepare("SELECT * FROM personalities WHERE user_id = ? ORDER BY created_at DESC");
$stmt->bind_param("i", $userId);
$stmt->execute();
$result = $stmt->get_result();
$personalities = [];
while ($row = $result->fetch_assoc()) {
$personalities[] = $row;
}
return $personalities;
}
/**
* Delete an AI personality and related conversations
*
* @param int $personalityId The ID of the personality to delete
* @param int $userId The user ID (for security verification)
* @return bool True if deletion was successful, false otherwise
**/
function deletePersonality(int $personalityId, int $userId): bool {
global $conn;
// Start a transaction to ensure data consistency
$conn->begin_transaction();
try {
// First, verify this personality belongs to the user (security check)
$stmt = $conn->prepare("SELECT id FROM personalities WHERE id = ? AND user_id = ?");
$stmt->bind_param("ii", $personalityId, $userId);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
// Personality doesn't exist or doesn't belong to this user
$conn->rollback();
return false;
}
// Get the avatar filename to delete from filesystem (if not default)
$stmt = $conn->prepare("SELECT avatar FROM personalities WHERE id = ?");
$stmt->bind_param("i", $personalityId);
$stmt->execute();
$avatarResult = $stmt->get_result();
$avatar = $avatarResult->fetch_assoc()['avatar'];
// Delete associated conversations and messages first
$stmt = $conn->prepare("DELETE FROM messages WHERE conversation_id IN (SELECT id FROM conversations WHERE personality_id = ?)");
$stmt->bind_param("i", $personalityId);
$stmt->execute();
$stmt = $conn->prepare("DELETE FROM conversations WHERE personality_id = ?");
$stmt->bind_param("i", $personalityId);
$stmt->execute();
// Now delete the personality
$stmt = $conn->prepare("DELETE FROM personalities WHERE id = ?");
$stmt->bind_param("i", $personalityId);
$stmt->execute();
// Commit the transaction
$conn->commit();
// Delete the avatar file if it's not the default
if ($avatar && $avatar !== 'default.png') {
$avatarPath = 'images/avatars/' . $avatar;
if (file_exists($avatarPath)) {
unlink($avatarPath);
}
}
return true;
} catch (Exception $e) {
// Something went wrong, rollback
$conn->rollback();
error_log("Error deleting personality: " . $e->getMessage());
return false;
}
}
/**
* Create a new conversation
*
* @param int $userId The user ID
* @param int $personalityId The personality ID
* @param string $title The conversation title
* @return int|false The conversation ID or false on failure
*/
function createConversation($userId, $personalityId, $title = 'New Conversation') {
global $conn;
$stmt = $conn->prepare("INSERT INTO conversations (user_id, personality_id, title) VALUES (?, ?, ?)");
$stmt->bind_param("iis", $userId, $personalityId, $title);
if ($stmt->execute()) {
return $conn->insert_id;
} else {
return false;
}
}
/**
* Add a message to a conversation
*
* @param int $conversationId The conversation ID
* @param string $role The message role (user or assistant)
* @param string $content The message content
* @return bool Success status
*/
function addMessage($conversationId, $role, $content) {
global $conn;
$stmt = $conn->prepare("INSERT INTO messages (conversation_id, role, content) VALUES (?, ?, ?)");
$stmt->bind_param("iss", $conversationId, $role, $content);
return $stmt->execute();
}
/**
* Get all messages for a conversation
*
* @param int $conversationId The conversation ID
* @return array List of messages
*/
function getConversationMessages($conversationId) {
global $conn;
$stmt = $conn->prepare("SELECT * FROM messages WHERE conversation_id = ? ORDER BY timestamp ASC");
$stmt->bind_param("i", $conversationId);
$stmt->execute();
$result = $stmt->get_result();
$messages = [];
while ($row = $result->fetch_assoc()) {
$messages[] = $row;
}
return $messages;
}
/**
* Get all conversations for a user
*
* @param int $userId The user ID
* @return array List of conversations
*/
function getUserConversations($userId) {
global $conn;
$stmt = $conn->prepare("
SELECT c.*, p.name as personality_name, p.avatar
FROM conversations c
JOIN personalities p ON c.personality_id = p.id
WHERE c.user_id = ?
ORDER BY c.created_at DESC
");
$stmt->bind_param("i", $userId);
$stmt->execute();
$result = $stmt->get_result();
$conversations = [];
while ($row = $result->fetch_assoc()) {
$conversations[] = $row;
}
return $conversations;
}
/**
* Get a personality by ID
*
* @param int $personalityId The personality ID
* @return array|false The personality data or false if not found
*/
function getPersonality($personalityId) {
global $conn;
$stmt = $conn->prepare("SELECT * FROM personalities WHERE id = ?");
$stmt->bind_param("i", $personalityId);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
return false;
}
return $result->fetch_assoc();
}