|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const getApiBase = () => {
|
|
|
const hostname = window.location.hostname;
|
|
|
const protocol = window.location.protocol;
|
|
|
|
|
|
console.log('[Nursing Council API] Detecting environment...');
|
|
|
console.log('[Nursing Council API] Hostname:', hostname);
|
|
|
console.log('[Nursing Council API] Protocol:', protocol);
|
|
|
|
|
|
|
|
|
if (hostname.includes('.app.github.dev')) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const match = hostname.match(/^(.+)-(\d+)(\.app\.github\.dev)$/);
|
|
|
|
|
|
if (match) {
|
|
|
const codespaceName = match[1];
|
|
|
const currentPort = match[2];
|
|
|
const domain = match[3];
|
|
|
console.log('[Nursing Council API] Codespace name:', codespaceName);
|
|
|
console.log('[Nursing Council API] Current port:', currentPort);
|
|
|
|
|
|
const backendHost = `${codespaceName}-8001${domain}`;
|
|
|
const apiBase = `https://${backendHost}`;
|
|
|
console.log('[Nursing Council API] Codespaces detected, API base:', apiBase);
|
|
|
return apiBase;
|
|
|
} else {
|
|
|
|
|
|
const backendHost = hostname.replace(/-\d+\.app\.github\.dev$/, '-8001.app.github.dev');
|
|
|
const apiBase = `https://${backendHost}`;
|
|
|
console.log('[Nursing Council API] Codespaces (fallback), API base:', apiBase);
|
|
|
return apiBase;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
if (hostname.includes(':5173') || hostname.includes('-5173')) {
|
|
|
const backendHost = hostname.replace(/5173/g, '8001');
|
|
|
const apiBase = `${protocol}//${backendHost}`;
|
|
|
console.log('[Nursing Council API] Dev environment detected, API base:', apiBase);
|
|
|
return apiBase;
|
|
|
}
|
|
|
|
|
|
|
|
|
if (hostname.includes('azurecontainerapps.io') || hostname.includes('hf.space')) {
|
|
|
console.log('[Nursing Council API] Production environment detected (Azure/HF)');
|
|
|
return '';
|
|
|
}
|
|
|
|
|
|
|
|
|
const apiBase = 'http://localhost:8001';
|
|
|
console.log('[Nursing Council API] Local development, API base:', apiBase);
|
|
|
return apiBase;
|
|
|
};
|
|
|
|
|
|
const API_BASE = getApiBase();
|
|
|
console.log('[Nursing Council API] Final API_BASE:', API_BASE);
|
|
|
|
|
|
export const api = {
|
|
|
|
|
|
|
|
|
|
|
|
async listConversations() {
|
|
|
const response = await fetch(`${API_BASE}/api/conversations`);
|
|
|
if (!response.ok) {
|
|
|
throw new Error('Failed to list conversations');
|
|
|
}
|
|
|
return response.json();
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async createConversation() {
|
|
|
const response = await fetch(`${API_BASE}/api/conversations`, {
|
|
|
method: 'POST',
|
|
|
headers: {
|
|
|
'Content-Type': 'application/json',
|
|
|
},
|
|
|
body: JSON.stringify({}),
|
|
|
});
|
|
|
if (!response.ok) {
|
|
|
throw new Error('Failed to create conversation');
|
|
|
}
|
|
|
return response.json();
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async getConversation(conversationId) {
|
|
|
const response = await fetch(
|
|
|
`${API_BASE}/api/conversations/${conversationId}`
|
|
|
);
|
|
|
if (!response.ok) {
|
|
|
throw new Error('Failed to get conversation');
|
|
|
}
|
|
|
return response.json();
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async sendMessage(conversationId, content, config = {}) {
|
|
|
const headers = {
|
|
|
'Content-Type': 'application/json',
|
|
|
'X-Provider': config.provider || '',
|
|
|
'X-Model': config.model || '',
|
|
|
'X-API-Key': config.apiKey || ''
|
|
|
};
|
|
|
|
|
|
const response = await fetch(
|
|
|
`${API_BASE}/api/conversations/${conversationId}/message`,
|
|
|
{
|
|
|
method: 'POST',
|
|
|
headers: headers,
|
|
|
body: JSON.stringify({ content }),
|
|
|
}
|
|
|
);
|
|
|
if (!response.ok) {
|
|
|
throw new Error('Failed to send message');
|
|
|
}
|
|
|
return response.json();
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async sendMessageStream(conversationId, content, onEvent, customRoles = [], config = {}) {
|
|
|
|
|
|
const customRolesToSend = customRoles.filter(r => r.isCustom).map(r => ({
|
|
|
id: r.id,
|
|
|
name: r.name,
|
|
|
description: r.description,
|
|
|
icon: r.icon || '👤'
|
|
|
}));
|
|
|
|
|
|
const headers = {
|
|
|
'Content-Type': 'application/json',
|
|
|
'X-Provider': config.provider || '',
|
|
|
'X-Model': config.model || '',
|
|
|
'X-API-Key': config.apiKey || ''
|
|
|
};
|
|
|
|
|
|
const response = await fetch(
|
|
|
`${API_BASE}/api/conversations/${conversationId}/message/stream`,
|
|
|
{
|
|
|
method: 'POST',
|
|
|
headers: headers,
|
|
|
body: JSON.stringify({
|
|
|
content,
|
|
|
custom_roles: customRolesToSend
|
|
|
}),
|
|
|
}
|
|
|
);
|
|
|
|
|
|
if (!response.ok) {
|
|
|
throw new Error('Failed to send message');
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
try {
|
|
|
const event = JSON.parse(data);
|
|
|
onEvent(event.type, event);
|
|
|
} catch (e) {
|
|
|
console.error('Failed to parse SSE event:', e);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async getCurrentUser() {
|
|
|
try {
|
|
|
if (API_BASE === '') {
|
|
|
|
|
|
const response = await fetch('/.auth/me');
|
|
|
if (response.ok) {
|
|
|
const payload = await response.json();
|
|
|
if (payload.length > 0) {
|
|
|
return payload[0].user_claims.find(c => c.typ === 'name')?.val || payload[0].user_id;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return null;
|
|
|
} catch (e) {
|
|
|
console.warn('Failed to fetch user:', e);
|
|
|
return null;
|
|
|
}
|
|
|
},
|
|
|
};
|
|
|
|