Initial commit

This commit is contained in:
2026-02-03 17:20:06 -05:00
commit 701e387a5b
16 changed files with 3453 additions and 0 deletions

98
frontend/js/api.js Normal file
View File

@@ -0,0 +1,98 @@
/**
* Chat Switchboard - API Module
* Handles communication with LLM backends
*/
const API = {
/**
* Fetch available models from endpoint
*/
async fetchModels(endpoint, apiKey) {
const response = await fetch(endpoint.replace(/\/$/, '') + '/models', {
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
const data = await response.json();
const models = data.data || data.models || data || [];
return Array.isArray(models)
? models.map(m => ({
id: m.id || m.name || m,
owned_by: m.owned_by || null
})).sort((a, b) => a.id.localeCompare(b.id))
: [];
},
/**
* Send chat completion request
*/
async sendMessage(settings, messages, onChunk = null) {
const endpoint = settings.apiEndpoint.replace(/\/$/, '') + '/chat/completions';
const controller = new AbortController();
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${settings.apiKey}`
},
body: JSON.stringify({
model: settings.model,
messages: messages.map(m => ({ role: m.role, content: m.content })),
max_tokens: settings.maxTokens,
temperature: settings.temperature,
top_p: settings.topP,
presence_penalty: settings.presencePenalty,
stream: settings.stream
}),
signal: controller.signal
});
if (!response.ok) {
const error = await response.text();
throw new Error(`API Error: ${response.status} - ${error}`);
}
let content = '';
if (settings.stream && onChunk) {
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') continue;
try {
const parsed = JSON.parse(data);
const delta = parsed.choices?.[0]?.delta?.content || '';
content += delta;
onChunk(content, delta);
} catch (e) {}
}
}
}
} else {
const data = await response.json();
content = data.choices?.[0]?.message?.content || 'No response';
}
return { content, controller };
}
};

108
frontend/js/storage.js Normal file
View File

@@ -0,0 +1,108 @@
/**
* Chat Switchboard - Storage Module
* Abstraction layer for storage (localStorage now, API later)
*/
const Storage = {
// Backend mode: 'local' or 'api'
mode: 'local',
apiBase: '/api',
async get(key, defaultValue = null) {
if (this.mode === 'api') {
try {
const response = await fetch(`${this.apiBase}/storage/${key}`);
if (response.ok) {
return await response.json();
}
return defaultValue;
} catch (e) {
console.error('Storage API get error:', e);
return defaultValue;
}
}
// Local storage
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue;
} catch (e) {
console.error('Storage get error:', e);
return defaultValue;
}
},
async set(key, value) {
if (this.mode === 'api') {
try {
await fetch(`${this.apiBase}/storage/${key}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(value)
});
} catch (e) {
console.error('Storage API set error:', e);
}
return;
}
// Local storage
try {
localStorage.setItem(key, JSON.stringify(value));
} catch (e) {
console.error('Storage set error:', e);
}
},
async remove(key) {
if (this.mode === 'api') {
try {
await fetch(`${this.apiBase}/storage/${key}`, { method: 'DELETE' });
} catch (e) {
console.error('Storage API remove error:', e);
}
return;
}
try {
localStorage.removeItem(key);
} catch (e) {
console.error('Storage remove error:', e);
}
},
// Switch to API mode
useApi(baseUrl = '/api') {
this.mode = 'api';
this.apiBase = baseUrl;
},
// Switch to local mode
useLocal() {
this.mode = 'local';
}
};
// For standalone HTML, use sync versions
const StorageSync = {
get(key, defaultValue = null) {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue;
} catch (e) {
return defaultValue;
}
},
set(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
} catch (e) {
console.error('Storage set error:', e);
}
},
remove(key) {
try {
localStorage.removeItem(key);
} catch (e) {}
}
};