302 lines
7.0 KiB
JavaScript
302 lines
7.0 KiB
JavaScript
const loginScreen = document.getElementById("loginScreen");
|
|
const welcomeScreen = document.getElementById("welcomeScreen");
|
|
const chatScreen = document.getElementById("chatScreen");
|
|
const chatMessages = document.getElementById("chatMessages");
|
|
const welcomeInput = document.getElementById("welcomeInput");
|
|
const chatInput = document.getElementById("chatInput");
|
|
const loginBtn = document.getElementById("loginBtn");
|
|
|
|
const API_URL = "http://localhost:8000";
|
|
|
|
let isInChat = false;
|
|
|
|
// Auth functions
|
|
function getToken() {
|
|
return localStorage.getItem("devden_token");
|
|
}
|
|
|
|
function setToken(token) {
|
|
localStorage.setItem("devden_token", token);
|
|
}
|
|
|
|
function clearToken() {
|
|
localStorage.removeItem("devden_token");
|
|
}
|
|
|
|
function showLoginScreen() {
|
|
loginScreen.classList.remove("hidden");
|
|
welcomeScreen.classList.add("hidden");
|
|
chatScreen.classList.add("hidden");
|
|
}
|
|
|
|
function showWelcomeScreen() {
|
|
loginScreen.classList.add("hidden");
|
|
welcomeScreen.classList.remove("hidden");
|
|
chatScreen.classList.add("hidden");
|
|
welcomeInput.focus();
|
|
}
|
|
|
|
function switchToChat() {
|
|
loginScreen.classList.add("hidden");
|
|
welcomeScreen.classList.add("hidden");
|
|
chatScreen.classList.remove("hidden");
|
|
chatInput.focus();
|
|
isInChat = true;
|
|
}
|
|
|
|
async function checkAuth() {
|
|
const token = getToken();
|
|
if (!token) {
|
|
showLoginScreen();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${API_URL}/api/auth/me`, {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
|
|
if (response.ok) {
|
|
showWelcomeScreen();
|
|
} else {
|
|
clearToken();
|
|
showLoginScreen();
|
|
}
|
|
} catch (error) {
|
|
console.error("Auth check failed:", error);
|
|
showLoginScreen();
|
|
}
|
|
}
|
|
|
|
async function handleLogin() {
|
|
loginBtn.disabled = true;
|
|
loginBtn.textContent = "Redirecting...";
|
|
|
|
try {
|
|
// Check if auth is configured
|
|
const statusResponse = await fetch(`${API_URL}/api/auth/status`);
|
|
const statusData = await statusResponse.json();
|
|
|
|
if (!statusData.configured) {
|
|
alert(
|
|
"Authentication not configured. Please set ENTRA_TENANT_ID, ENTRA_CLIENT_ID, and ENTRA_CLIENT_SECRET in your .env file.",
|
|
);
|
|
loginBtn.disabled = false;
|
|
loginBtn.textContent = "Sign in with Microsoft";
|
|
return;
|
|
}
|
|
|
|
// Get auth URL and redirect
|
|
const response = await fetch(`${API_URL}/api/auth/login`);
|
|
const data = await response.json();
|
|
|
|
if (data.auth_url) {
|
|
window.location.href = data.auth_url;
|
|
} else {
|
|
throw new Error("No auth URL returned");
|
|
}
|
|
} catch (error) {
|
|
console.error("Login failed:", error);
|
|
alert("Login failed: " + error.message);
|
|
loginBtn.disabled = false;
|
|
loginBtn.textContent = "Sign in with Microsoft";
|
|
}
|
|
}
|
|
|
|
async function handleCallback() {
|
|
const params = new URLSearchParams(window.location.search);
|
|
const code = params.get("code");
|
|
|
|
if (!code) return false;
|
|
|
|
try {
|
|
const response = await fetch(`${API_URL}/api/auth/callback`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ code }),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.detail || "Callback failed");
|
|
}
|
|
|
|
const data = await response.json();
|
|
setToken(data.token);
|
|
|
|
// Clean up URL
|
|
window.history.replaceState({}, "", "/");
|
|
|
|
return true;
|
|
} catch (error) {
|
|
console.error("Callback failed:", error);
|
|
alert("Authentication failed: " + error.message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Chat functions
|
|
function addMessage(text, type = "user") {
|
|
const msg = document.createElement("div");
|
|
msg.className = `message ${type}`;
|
|
msg.innerHTML = `<div class="message-text">${escapeHtml(text)}</div>`;
|
|
chatMessages.appendChild(msg);
|
|
scrollToBottom();
|
|
}
|
|
|
|
function showTyping() {
|
|
const typing = document.createElement("div");
|
|
typing.className = "message assistant";
|
|
typing.id = "typing";
|
|
typing.innerHTML = `
|
|
<div class="message-text">
|
|
<div class="typing">
|
|
<span></span>
|
|
<span></span>
|
|
<span></span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
chatMessages.appendChild(typing);
|
|
scrollToBottom();
|
|
}
|
|
|
|
function hideTyping() {
|
|
const typing = document.getElementById("typing");
|
|
if (typing) typing.remove();
|
|
}
|
|
|
|
function scrollToBottom() {
|
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
const div = document.createElement("div");
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
async function sendMessage(text) {
|
|
if (!text.trim()) return;
|
|
|
|
if (!isInChat) {
|
|
switchToChat();
|
|
}
|
|
|
|
addMessage(text, "user");
|
|
chatInput.value = "";
|
|
chatInput.disabled = true;
|
|
|
|
showTyping();
|
|
|
|
try {
|
|
const token = getToken();
|
|
const response = await fetch(`${API_URL}/api/chat/stream`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify({
|
|
message: text,
|
|
provider: null,
|
|
}),
|
|
});
|
|
|
|
if (response.status === 401) {
|
|
hideTyping();
|
|
clearToken();
|
|
showLoginScreen();
|
|
return;
|
|
}
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
hideTyping();
|
|
|
|
// Create message element for streaming
|
|
const msgDiv = document.createElement("div");
|
|
msgDiv.className = "message assistant";
|
|
const textDiv = document.createElement("div");
|
|
textDiv.className = "message-text";
|
|
msgDiv.appendChild(textDiv);
|
|
chatMessages.appendChild(msgDiv);
|
|
|
|
// Read stream
|
|
const reader = response.body.getReader();
|
|
const decoder = new TextDecoder();
|
|
let assistantMessage = "";
|
|
|
|
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 = JSON.parse(line.slice(6));
|
|
|
|
if (data.error) {
|
|
textDiv.textContent = `Error: ${data.error}`;
|
|
break;
|
|
}
|
|
|
|
if (data.chunk) {
|
|
assistantMessage += data.chunk;
|
|
textDiv.textContent = assistantMessage;
|
|
scrollToBottom();
|
|
}
|
|
|
|
if (data.done) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
hideTyping();
|
|
addMessage(`Error: ${error.message}`, "assistant");
|
|
} finally {
|
|
chatInput.disabled = false;
|
|
chatInput.focus();
|
|
}
|
|
}
|
|
|
|
// Event listeners
|
|
loginBtn.addEventListener("click", handleLogin);
|
|
|
|
welcomeInput.addEventListener("keydown", (e) => {
|
|
if (e.key === "Enter") {
|
|
e.preventDefault();
|
|
const text = welcomeInput.value.trim();
|
|
if (text) {
|
|
sendMessage(text);
|
|
}
|
|
}
|
|
});
|
|
|
|
chatInput.addEventListener("keydown", (e) => {
|
|
if (e.key === "Enter") {
|
|
e.preventDefault();
|
|
sendMessage(chatInput.value);
|
|
}
|
|
});
|
|
|
|
// Initialize
|
|
async function init() {
|
|
// Check for OAuth callback first
|
|
const callbackSuccess = await handleCallback();
|
|
|
|
if (callbackSuccess) {
|
|
showWelcomeScreen();
|
|
} else {
|
|
await checkAuth();
|
|
}
|
|
}
|
|
|
|
init();
|