Migrate storage to JSON and add robust client-side save error handling

main
Steffen Pohle 2 weeks ago
parent 7a40c6f603
commit 84ab5c4303

@ -167,11 +167,11 @@ function sanitizeUsername($username) {
} }
function getUserConfigPath($username) { function getUserConfigPath($username) {
return USER_CONFIG_DIR . '/' . sanitizeUsername($username) . '-config.php'; return USER_CONFIG_DIR . '/' . sanitizeUsername($username) . '-config.json';
} }
function getUserChatPath($username) { function getUserChatPath($username) {
return USER_CONFIG_DIR . '/' . sanitizeUsername($username) . '-chat.php'; return USER_CONFIG_DIR . '/' . sanitizeUsername($username) . '-chat.json';
} }
function getConfiguredDefaultModel() { function getConfiguredDefaultModel() {
@ -188,11 +188,30 @@ function getDefaultModelForUser($username) {
function readUserConfig($username) { function readUserConfig($username) {
$path = getUserConfigPath($username); $path = getUserConfigPath($username);
if (!file_exists($path)) { if (!file_exists($path)) {
// Try migrating from legacy PHP file if present
$legacy = USER_CONFIG_DIR . '/' . sanitizeUsername($username) . '-config.php';
if (file_exists($legacy)) {
$raw = @file_get_contents($legacy);
if ($raw !== false) {
// attempt to include safely
$data = @include $legacy;
if (is_array($data)) {
// write JSON migration
writeUserConfig($username, $data);
return $data;
}
}
}
return [];
}
$raw = @file_get_contents($path);
if ($raw === false) {
return []; return [];
} }
$data = include $path; $decoded = json_decode($raw, true);
return is_array($data) ? $data : []; return is_array($decoded) ? $decoded : [];
} }
function writeUserConfig($username, $config) { function writeUserConfig($username, $config) {
@ -202,7 +221,6 @@ function writeUserConfig($username, $config) {
if ($username === '') { if ($username === '') {
return false; return false;
} }
$data = is_array($config) ? $config : []; $data = is_array($config) ? $config : [];
$data['username'] = $username; $data['username'] = $username;
$data['role'] = (string) ($data['role'] ?? 'user'); $data['role'] = (string) ($data['role'] ?? 'user');
@ -211,18 +229,41 @@ function writeUserConfig($username, $config) {
$storedDefaultModel = trim((string) ($data['defaultModel'] ?? '')); $storedDefaultModel = trim((string) ($data['defaultModel'] ?? ''));
$data['defaultModel'] = $storedDefaultModel !== '' ? $storedDefaultModel : getConfiguredDefaultModel(); $data['defaultModel'] = $storedDefaultModel !== '' ? $storedDefaultModel : getConfiguredDefaultModel();
$php = "<?php\n\nreturn " . var_export($data, true) . ";\n"; $json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
return file_put_contents(getUserConfigPath($username), $php) !== false; if ($json === false) {
return false;
}
$path = getUserConfigPath($username);
$tmp = $path . '.tmp';
if (@file_put_contents($tmp, $json . "\n") === false) {
return false;
}
return @rename($tmp, $path);
} }
function readUserChat($username) { function readUserChat($username) {
$path = getUserChatPath($username); $path = getUserChatPath($username);
if (!file_exists($path)) { if (!file_exists($path)) {
// Try migrating legacy PHP chat file
$legacy = USER_CONFIG_DIR . '/' . sanitizeUsername($username) . '-chat.php';
if (file_exists($legacy)) {
$data = @include $legacy;
if (is_array($data)) {
writeUserChat($username, $data);
return $data;
}
}
return []; return [];
} }
$data = include $path; $raw = @file_get_contents($path);
return is_array($data) ? $data : []; if ($raw === false) {
return [];
}
$decoded = json_decode($raw, true);
return is_array($decoded) ? $decoded : [];
} }
function writeUserChat($username, $chats) { function writeUserChat($username, $chats) {
@ -232,9 +273,17 @@ function writeUserChat($username, $chats) {
if ($username === '') { if ($username === '') {
return false; return false;
} }
$json = json_encode(is_array($chats) ? $chats : [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
if ($json === false) {
return false;
}
$php = "<?php\n\nreturn " . var_export(is_array($chats) ? $chats : [], true) . ";\n"; $path = getUserChatPath($username);
return file_put_contents(getUserChatPath($username), $php) !== false; $tmp = $path . '.tmp';
if (@file_put_contents($tmp, $json . "\n") === false) {
return false;
}
return @rename($tmp, $path);
} }
function renameUserFiles($oldUsername, $newUsername) { function renameUserFiles($oldUsername, $newUsername) {
@ -281,21 +330,47 @@ function saveUserConfigData($username, $config) {
function loadUsers() { function loadUsers() {
ensureConfigDirectory(); ensureConfigDirectory();
$files = glob(USER_CONFIG_DIR . '/*-config.json');
$files = glob(USER_CONFIG_DIR . '/*-config.php');
if ($files === false) { if ($files === false) {
return []; $files = [];
} }
$users = []; $users = [];
// load json config files
foreach ($files as $file) { foreach ($files as $file) {
$username = basename($file, '-config.php'); $username = basename($file, '-config.json');
$user = readUserConfig($username); $user = readUserConfig($username);
if (!empty($user) && !empty($user['username'])) { if (!empty($user) && !empty($user['username'])) {
$users[] = $user; $users[] = $user;
} }
} }
// detect legacy php config files and migrate
$legacyFiles = glob(USER_CONFIG_DIR . '/*-config.php');
if ($legacyFiles !== false) {
foreach ($legacyFiles as $legacy) {
$username = basename($legacy, '-config.php');
// if already loaded from json, skip
$exists = false;
foreach ($users as $u) {
if (($u['username'] ?? '') === $username) {
$exists = true;
break;
}
}
if ($exists) continue;
$data = @include $legacy;
if (is_array($data) && !empty($data['username'])) {
// migrate to json
writeUserConfig($username, $data);
// remove legacy files
@unlink($legacy);
$users[] = $data;
}
}
}
usort($users, function ($a, $b) { usort($users, function ($a, $b) {
return strcmp(($a['username'] ?? ''), ($b['username'] ?? '')); return strcmp(($a['username'] ?? ''), ($b['username'] ?? ''));
}); });
@ -340,11 +415,13 @@ function saveUsers($users, $previousUsers = null) {
$currentUsernames[] = sanitizeUsername($entry['username']); $currentUsernames[] = sanitizeUsername($entry['username']);
} }
$files = glob(USER_CONFIG_DIR . '/*-config.php'); $files = glob(USER_CONFIG_DIR . '/*-config.json');
if ($files !== false) { if ($files !== false) {
foreach ($files as $file) { foreach ($files as $file) {
$name = basename($file, '-config.php'); $name = basename($file, '-config.json');
if (!in_array($name, $currentUsernames, true)) { if (!in_array($name, $currentUsernames, true)) {
@unlink(USER_CONFIG_DIR . '/' . $name . '-config.json');
@unlink(USER_CONFIG_DIR . '/' . $name . '-chat.json');
@unlink(USER_CONFIG_DIR . '/' . $name . '-config.php'); @unlink(USER_CONFIG_DIR . '/' . $name . '-config.php');
@unlink(USER_CONFIG_DIR . '/' . $name . '-chat.php'); @unlink(USER_CONFIG_DIR . '/' . $name . '-chat.php');
} }

@ -207,9 +207,26 @@ async function saveChat() {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: currentChatId, model: modelSelect.value, messages: chatHistory }) body: JSON.stringify({ id: currentChatId, model: modelSelect.value, messages: chatHistory })
}); });
const result = await response.json(); let result = null;
if (result.id) currentChatId = result.id; try {
loadHistory(); result = await response.json();
} catch (e) {
console.error('Failed to parse save response', e);
const text = await response.text();
console.error('Raw save response:', text);
throw new Error('Save failed: invalid server response');
}
console.debug('Save response:', result);
if (result && result.error) {
console.error('Save error:', result.error);
alert('Chat save failed: ' + result.error);
throw new Error(result.error);
}
if (result && result.id) currentChatId = result.id;
await loadHistory();
return result;
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} }

Loading…
Cancel
Save