Initial commit
This commit is contained in:
98
frontend/js/api.js
Normal file
98
frontend/js/api.js
Normal 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
108
frontend/js/storage.js
Normal 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) {}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user