1168 lines
No EOL
32 KiB
HTML
1168 lines
No EOL
32 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>Policy Chatbot</title>
|
||
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||
<!-- MSAL.js library for Microsoft authentication -->
|
||
<script src="https://alcdn.msauth.net/browser/2.38.0/js/msal-browser.min.js"></script>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: 'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||
background: linear-gradient(135deg, #2c2c2c 0%, #1a1a1a 100%);
|
||
min-height: 100vh;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
padding: 20px;
|
||
}
|
||
|
||
.chat-container {
|
||
width: 100%;
|
||
max-width: 800px;
|
||
height: 85vh;
|
||
background: rgba(255, 255, 255, 0.95);
|
||
backdrop-filter: blur(20px);
|
||
border-radius: 20px;
|
||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
}
|
||
|
||
.chat-header {
|
||
background: linear-gradient(135deg, #FFC407 0%, #e6b007 100%);
|
||
color: #1a1a1a;
|
||
padding: 20px 25px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
position: relative;
|
||
}
|
||
|
||
.chat-header::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.1) 50%, transparent 70%);
|
||
animation: shimmer 3s infinite;
|
||
}
|
||
|
||
@keyframes shimmer {
|
||
0% { transform: translateX(-100%); }
|
||
100% { transform: translateX(100%); }
|
||
}
|
||
|
||
.header-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
z-index: 1;
|
||
}
|
||
|
||
.ai-avatar {
|
||
width: 40px;
|
||
height: 40px;
|
||
background: linear-gradient(135deg, #FFC407, #ffcf33);
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 18px;
|
||
animation: pulse 2s infinite;
|
||
}
|
||
|
||
@keyframes pulse {
|
||
0%, 100% { transform: scale(1); }
|
||
50% { transform: scale(1.05); }
|
||
}
|
||
|
||
.header-info h1 {
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.status {
|
||
font-size: 12px;
|
||
opacity: 0.9;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
|
||
.status-dot {
|
||
width: 8px;
|
||
height: 8px;
|
||
background: #4ade80;
|
||
border-radius: 50%;
|
||
animation: blink 1.5s infinite;
|
||
}
|
||
|
||
@keyframes blink {
|
||
0%, 50% { opacity: 1; }
|
||
51%, 100% { opacity: 0.3; }
|
||
}
|
||
|
||
.chat-body {
|
||
flex: 1;
|
||
padding: 20px;
|
||
overflow-y: auto;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
background: linear-gradient(180deg, #fafafa 0%, #f8fafc 100%);
|
||
}
|
||
|
||
.chat-body::-webkit-scrollbar {
|
||
width: 6px;
|
||
}
|
||
|
||
.chat-body::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
|
||
.chat-body::-webkit-scrollbar-thumb {
|
||
background: rgba(0, 0, 0, 0.2);
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.message {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 12px;
|
||
animation: slideIn 0.3s ease-out;
|
||
opacity: 0;
|
||
animation-fill-mode: forwards;
|
||
}
|
||
|
||
@keyframes slideIn {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(20px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.message-avatar {
|
||
width: 36px;
|
||
height: 36px;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 16px;
|
||
flex-shrink: 0;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.user .message-avatar {
|
||
background: linear-gradient(135deg, #2c2c2c, #1a1a1a);
|
||
color: white;
|
||
order: 2;
|
||
}
|
||
|
||
.ai .message-avatar {
|
||
background: linear-gradient(135deg, #FFC407, #e6b007);
|
||
color: #1a1a1a;
|
||
}
|
||
|
||
.message-content {
|
||
max-width: 70%;
|
||
padding: 16px 20px;
|
||
border-radius: 18px;
|
||
position: relative;
|
||
word-wrap: break-word;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.user .message-content {
|
||
background: linear-gradient(135deg, #2c2c2c, #1a1a1a);
|
||
color: white;
|
||
border-bottom-right-radius: 4px;
|
||
margin-left: auto;
|
||
order: 1;
|
||
}
|
||
|
||
.ai .message-content {
|
||
background: white;
|
||
color: #1f2937;
|
||
border-bottom-left-radius: 4px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
/* AI Response Formatting */
|
||
.bullet-point {
|
||
margin: 8px 0;
|
||
padding-left: 12px;
|
||
color: #374151;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.numbered-point {
|
||
margin: 8px 0;
|
||
padding-left: 8px;
|
||
color: #374151;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.numbered-point .number {
|
||
font-weight: 600;
|
||
color: #FFC407;
|
||
margin-right: 6px;
|
||
}
|
||
|
||
.section-header {
|
||
font-weight: 700;
|
||
color: #1f2937;
|
||
margin: 16px 0 8px 0;
|
||
font-size: 15px;
|
||
border-bottom: 2px solid #FFC407;
|
||
padding-bottom: 4px;
|
||
}
|
||
|
||
.sub-bullet {
|
||
margin: 4px 0;
|
||
padding-left: 24px;
|
||
color: #6b7280;
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.paragraph-break {
|
||
margin: 12px 0;
|
||
}
|
||
|
||
.policy-ref {
|
||
background: #f3f4f6;
|
||
color: #6b7280;
|
||
padding: 2px 6px;
|
||
border-radius: 4px;
|
||
font-size: 13px;
|
||
font-style: italic;
|
||
}
|
||
|
||
.message-content strong {
|
||
color: #1f2937;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.message-content em {
|
||
color: #FFC407;
|
||
font-style: italic;
|
||
}
|
||
|
||
.user {
|
||
flex-direction: row-reverse;
|
||
}
|
||
|
||
.message-time {
|
||
font-size: 11px;
|
||
opacity: 0.6;
|
||
margin-top: 4px;
|
||
text-align: right;
|
||
}
|
||
|
||
.user .message-time {
|
||
color: rgba(255, 255, 255, 0.8);
|
||
}
|
||
|
||
.ai .message-time {
|
||
color: #6b7280;
|
||
text-align: left;
|
||
}
|
||
|
||
.chat-footer {
|
||
padding: 20px 25px;
|
||
background: white;
|
||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: flex-end;
|
||
}
|
||
|
||
.input-container {
|
||
flex: 1;
|
||
position: relative;
|
||
}
|
||
|
||
.chat-input {
|
||
width: 100%;
|
||
padding: 16px 50px 16px 20px;
|
||
border: 2px solid #e5e7eb;
|
||
border-radius: 25px;
|
||
font-size: 15px;
|
||
font-family: inherit;
|
||
outline: none;
|
||
transition: all 0.3s ease;
|
||
background: #f9fafb;
|
||
resize: none;
|
||
min-height: 50px;
|
||
max-height: 120px;
|
||
}
|
||
|
||
.chat-input:focus {
|
||
border-color: #FFC407;
|
||
background: white;
|
||
box-shadow: 0 0 0 3px rgba(255, 196, 7, 0.2);
|
||
}
|
||
|
||
.send-button {
|
||
position: absolute;
|
||
right: 8px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
width: 36px;
|
||
height: 36px;
|
||
border: none;
|
||
border-radius: 50%;
|
||
background: linear-gradient(135deg, #FFC407, #e6b007);
|
||
color: #1a1a1a;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: all 0.3s ease;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.send-button:hover {
|
||
transform: translateY(-50%) scale(1.1);
|
||
box-shadow: 0 4px 12px rgba(255, 196, 7, 0.4);
|
||
}
|
||
|
||
.send-button:disabled {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
transform: translateY(-50%) scale(1);
|
||
}
|
||
|
||
.typing-indicator {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 16px 0;
|
||
opacity: 0;
|
||
transform: translateY(10px);
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.typing-indicator.show {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
|
||
.typing-dots {
|
||
display: flex;
|
||
gap: 4px;
|
||
}
|
||
|
||
.typing-dot {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 50%;
|
||
background: #6b7280;
|
||
animation: typingAnimation 1.4s infinite ease-in-out;
|
||
}
|
||
|
||
.typing-dot:nth-child(1) { animation-delay: -0.32s; }
|
||
.typing-dot:nth-child(2) { animation-delay: -0.16s; }
|
||
.typing-dot:nth-child(3) { animation-delay: 0s; }
|
||
|
||
@keyframes typingAnimation {
|
||
0%, 80%, 100% {
|
||
transform: scale(0);
|
||
opacity: 0.5;
|
||
}
|
||
40% {
|
||
transform: scale(1);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
.welcome-message {
|
||
text-align: center;
|
||
padding: 40px 20px;
|
||
color: #6b7280;
|
||
}
|
||
|
||
.welcome-message h2 {
|
||
font-size: 24px;
|
||
margin-bottom: 8px;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.welcome-message p {
|
||
font-size: 16px;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
/* Auth button styles */
|
||
.auth-container {
|
||
z-index: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
.auth-button {
|
||
padding: 8px 20px;
|
||
border: 2px solid #1a1a1a;
|
||
border-radius: 20px;
|
||
background: white;
|
||
color: #1a1a1a;
|
||
font-weight: 600;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.auth-button:hover {
|
||
background: #1a1a1a;
|
||
color: white;
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
.user-profile {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 6px 12px;
|
||
background: rgba(255, 255, 255, 0.9);
|
||
border-radius: 20px;
|
||
border: 2px solid #1a1a1a;
|
||
}
|
||
|
||
.user-avatar {
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 50%;
|
||
background: #1a1a1a;
|
||
color: white;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-weight: 600;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.user-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
}
|
||
|
||
.user-name {
|
||
font-weight: 600;
|
||
font-size: 14px;
|
||
color: #1a1a1a;
|
||
}
|
||
|
||
.user-email {
|
||
font-size: 11px;
|
||
color: #6b7280;
|
||
}
|
||
|
||
.logout-button {
|
||
padding: 6px 14px;
|
||
border: none;
|
||
border-radius: 15px;
|
||
background: #1a1a1a;
|
||
color: white;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.logout-button:hover {
|
||
background: #dc2626;
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
.hidden {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Login Screen Overlay */
|
||
.login-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: linear-gradient(135deg, #2c2c2c 0%, #1a1a1a 100%);
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
z-index: 9999;
|
||
}
|
||
|
||
.login-card {
|
||
background: white;
|
||
padding: 60px 50px;
|
||
border-radius: 20px;
|
||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||
text-align: center;
|
||
max-width: 450px;
|
||
width: 90%;
|
||
}
|
||
|
||
.login-card .logo {
|
||
font-size: 64px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.login-card h1 {
|
||
font-size: 28px;
|
||
color: #1a1a1a;
|
||
margin-bottom: 10px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.login-card p {
|
||
font-size: 16px;
|
||
color: #6b7280;
|
||
margin-bottom: 40px;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.login-card .auth-button {
|
||
width: 100%;
|
||
padding: 16px 24px;
|
||
font-size: 16px;
|
||
justify-content: center;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.app-blur {
|
||
filter: blur(10px);
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* Адаптивность */
|
||
@media (max-width: 768px) {
|
||
body { padding: 10px; }
|
||
.chat-container {
|
||
height: 95vh;
|
||
border-radius: 15px;
|
||
}
|
||
.chat-header { padding: 15px 20px; }
|
||
.header-info h1 { font-size: 18px; }
|
||
.chat-body { padding: 15px; }
|
||
.chat-footer { padding: 15px 20px; }
|
||
.message-content { max-width: 85%; }
|
||
}
|
||
|
||
@media (max-width: 480px) {
|
||
.chat-header { padding: 12px 16px; }
|
||
.header-info h1 { font-size: 16px; }
|
||
.chat-body { padding: 12px; }
|
||
.chat-footer { padding: 12px 16px; }
|
||
.message-content {
|
||
max-width: 90%;
|
||
padding: 12px 16px;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<!-- Login Screen Overlay -->
|
||
<div id="loginOverlay" class="login-overlay">
|
||
<div class="login-card">
|
||
<div class="logo">🛡️</div>
|
||
<h1>Oliver Compliance Hub</h1>
|
||
<p>Please sign in with your Microsoft account to access the compliance chat assistant.</p>
|
||
<p id="iframeNotice" style="font-size: 13px; color: #f59e0b; margin-bottom: 15px; display: none;">
|
||
ℹ️ Running in SharePoint. A popup window will open for authentication.
|
||
</p>
|
||
<button class="auth-button" onclick="signIn()">
|
||
<span>🔐</span>
|
||
<span>Sign in with Microsoft</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="appContainer" class="chat-container app-blur">
|
||
<div class="chat-header">
|
||
<div class="header-left">
|
||
<div class="ai-avatar">🤖</div>
|
||
<div class="header-info">
|
||
<h1>Policy Chatbot</h1>
|
||
<div class="status">
|
||
<div class="status-dot"></div>
|
||
<span>Online</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="auth-container">
|
||
<button id="loginButton" class="auth-button" onclick="signIn()">
|
||
<span>🔐</span>
|
||
<span>Sign in with Microsoft</span>
|
||
</button>
|
||
<div id="userProfile" class="user-profile hidden">
|
||
<div class="user-avatar" id="userAvatar"></div>
|
||
<div class="user-info">
|
||
<div class="user-name" id="userName"></div>
|
||
<div class="user-email" id="userEmail"></div>
|
||
</div>
|
||
<button class="logout-button" onclick="signOut()">Logout</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="chat" class="chat-body">
|
||
<div class="welcome-message">
|
||
<h2>Welcome to Oliver Compliance Hub! 🛡️</h2>
|
||
<p>Your friendly policy guide is here! Ask me anything about Oliver's compliance policies and procedures.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="typingIndicator" class="typing-indicator">
|
||
<div class="message-avatar" style="background: linear-gradient(135deg, #FFC407, #e6b007); color: #1a1a1a;">🤖</div>
|
||
<div style="color: #6b7280;">
|
||
Policy Chatbot is typing
|
||
<div class="typing-dots">
|
||
<div class="typing-dot"></div>
|
||
<div class="typing-dot"></div>
|
||
<div class="typing-dot"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="chat-footer">
|
||
<div class="input-container">
|
||
<textarea
|
||
id="message"
|
||
class="chat-input"
|
||
placeholder="Type your message..."
|
||
rows="1"
|
||
></textarea>
|
||
<button id="sendBtn" class="send-button" onclick="sendMessage()">
|
||
➤
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// Check if running inside iframe
|
||
const isInIframe = window.self !== window.top;
|
||
console.log('Running in iframe:', isInIframe);
|
||
|
||
// Show iframe notice if applicable
|
||
if (isInIframe) {
|
||
const iframeNotice = document.getElementById('iframeNotice');
|
||
if (iframeNotice) {
|
||
iframeNotice.style.display = 'block';
|
||
}
|
||
}
|
||
|
||
// MSAL Configuration for Microsoft SSO with PKCE flow
|
||
// Using popup mode for iframe compatibility
|
||
const msalConfig = {
|
||
auth: {
|
||
clientId: "9079054c-9620-4757-a256-23413042f1ef",
|
||
authority: "https://login.microsoftonline.com/e519c2e6-bc6d-4fdf-8d9c-923c2f002385",
|
||
redirectUri: window.location.origin + window.location.pathname, // Use current page URL
|
||
navigateToLoginRequestUrl: false // Always disable to prevent navigation after popup
|
||
},
|
||
cache: {
|
||
cacheLocation: "sessionStorage", // Use sessionStorage for iframe compatibility
|
||
storeAuthStateInCookie: true // Store in cookies for cross-tab support
|
||
},
|
||
system: {
|
||
allowNativeBroker: false, // Disables WAM Broker
|
||
allowRedirectInIframe: false, // Explicitly disable redirects in iframe
|
||
windowHashTimeout: 60000, // Increase timeout for popup window (60s)
|
||
iframeHashTimeout: 6000,
|
||
loadFrameTimeout: 0,
|
||
asyncPopups: false, // Use synchronous popups for better control
|
||
loggerOptions: {
|
||
loggerCallback: (level, message, containsPii) => {
|
||
if (containsPii) return;
|
||
console.log(message);
|
||
},
|
||
piiLoggingEnabled: false,
|
||
logLevel: msal.LogLevel.Info
|
||
}
|
||
}
|
||
};
|
||
|
||
// Request scopes for login
|
||
const loginRequest = {
|
||
scopes: ["User.Read", "openid", "profile", "email"],
|
||
// Popup window configuration for better UX
|
||
popupWindowAttributes: {
|
||
popupSize: {
|
||
height: 600,
|
||
width: 500
|
||
},
|
||
popupPosition: {
|
||
top: 100,
|
||
left: 100
|
||
}
|
||
}
|
||
};
|
||
|
||
// Initialize MSAL instance
|
||
const msalInstance = new msal.PublicClientApplication(msalConfig);
|
||
|
||
// Initialize MSAL and handle redirect response
|
||
let currentAccount = null;
|
||
|
||
msalInstance.initialize().then(() => {
|
||
// Always handle redirect promise to process popup returns
|
||
return msalInstance.handleRedirectPromise();
|
||
}).then((response) => {
|
||
if (response) {
|
||
console.log('Redirect response received:', response);
|
||
currentAccount = response.account;
|
||
|
||
// If we're in a popup window (opened by parent for auth), just close
|
||
if (window.opener && window.opener !== window) {
|
||
console.log('Auth successful in popup, closing...');
|
||
window.close();
|
||
return;
|
||
}
|
||
|
||
updateUIForSignedInUser(currentAccount);
|
||
} else {
|
||
// Check if user is already signed in
|
||
const accounts = msalInstance.getAllAccounts();
|
||
if (accounts.length > 0) {
|
||
currentAccount = accounts[0];
|
||
updateUIForSignedInUser(currentAccount);
|
||
} else {
|
||
// No user signed in - keep login overlay visible
|
||
disableChatInterface();
|
||
}
|
||
}
|
||
}).catch((error) => {
|
||
console.error("MSAL initialization error:", error);
|
||
|
||
// If we're in a popup window, try to close on error too
|
||
if (window.opener && window.opener !== window) {
|
||
console.log('Error in popup, closing...');
|
||
window.close();
|
||
return;
|
||
}
|
||
|
||
disableChatInterface();
|
||
});
|
||
|
||
// Sign In function using popup (works in iframe)
|
||
async function signIn() {
|
||
try {
|
||
console.log('Starting login with popup...');
|
||
console.log('IsInIframe:', isInIframe);
|
||
|
||
const response = await msalInstance.loginPopup({
|
||
...loginRequest,
|
||
redirectUri: window.location.origin + window.location.pathname,
|
||
// Ensure popup stays open until auth completes
|
||
prompt: 'select_account'
|
||
});
|
||
|
||
if (response && response.account) {
|
||
console.log('Login successful!', response);
|
||
currentAccount = response.account;
|
||
updateUIForSignedInUser(currentAccount);
|
||
} else {
|
||
console.warn('Login response received but no account:', response);
|
||
}
|
||
} catch (error) {
|
||
console.error("Login error:", error);
|
||
|
||
// Better error handling for iframe/popup issues
|
||
if (error.errorCode === 'popup_window_error' || error.errorCode === 'empty_window_error') {
|
||
alert('Popup was blocked. Please allow popups for this site and try again.\n\nОкно авторизации было заблокировано. Пожалуйста, разрешите всплывающие окна для этого сайта и попробуйте снова.');
|
||
} else if (error.errorCode === 'user_cancelled') {
|
||
console.log('User cancelled login');
|
||
} else if (error.errorCode === 'monitor_window_timeout') {
|
||
alert('Authentication timed out. The popup window was closed before login completed.\n\nВремя авторизации истекло. Окно было закрыто до завершения входа.');
|
||
} else {
|
||
alert("Login failed: " + error.message + "\n\nError code: " + (error.errorCode || 'unknown'));
|
||
}
|
||
}
|
||
}
|
||
|
||
// Sign Out function - LOCAL LOGOUT ONLY (does not sign out of Microsoft account)
|
||
function signOut() {
|
||
// Remove account from MSAL cache (local logout only)
|
||
if (currentAccount) {
|
||
const logoutRequest = {
|
||
account: currentAccount
|
||
};
|
||
msalInstance.logoutPopup(logoutRequest).catch(() => {
|
||
// If popup fails, do manual cleanup
|
||
msalInstance.clearCache();
|
||
});
|
||
}
|
||
|
||
// Clear local state
|
||
currentAccount = null;
|
||
|
||
// Reset UI to login state
|
||
disableChatInterface();
|
||
|
||
// Clear chat history
|
||
const chat = document.getElementById("chat");
|
||
chat.innerHTML = '';
|
||
}
|
||
|
||
// Update UI for signed-in user
|
||
function updateUIForSignedInUser(account) {
|
||
if (account) {
|
||
const loginButton = document.getElementById("loginButton");
|
||
const userProfile = document.getElementById("userProfile");
|
||
const userName = document.getElementById("userName");
|
||
const userEmail = document.getElementById("userEmail");
|
||
const userAvatar = document.getElementById("userAvatar");
|
||
|
||
// Hide login button, show user profile
|
||
loginButton.classList.add("hidden");
|
||
userProfile.classList.remove("hidden");
|
||
|
||
// Populate user info
|
||
userName.textContent = account.name || "User";
|
||
userEmail.textContent = account.username || "";
|
||
|
||
// Set avatar with first letter of name
|
||
const firstLetter = account.name ? account.name.charAt(0).toUpperCase() : "U";
|
||
userAvatar.textContent = firstLetter;
|
||
|
||
// Enable app access
|
||
enableChatInterface();
|
||
}
|
||
}
|
||
|
||
// Enable chat interface after successful login
|
||
function enableChatInterface() {
|
||
const loginOverlay = document.getElementById("loginOverlay");
|
||
const appContainer = document.getElementById("appContainer");
|
||
const messageInput = document.getElementById("message");
|
||
const sendBtn = document.getElementById("sendBtn");
|
||
|
||
// Hide login overlay and show app
|
||
loginOverlay.classList.add("hidden");
|
||
appContainer.classList.remove("app-blur");
|
||
|
||
// Enable chat controls
|
||
messageInput.disabled = false;
|
||
sendBtn.disabled = false;
|
||
messageInput.focus();
|
||
}
|
||
|
||
// Disable chat interface (show login screen)
|
||
function disableChatInterface() {
|
||
const loginOverlay = document.getElementById("loginOverlay");
|
||
const appContainer = document.getElementById("appContainer");
|
||
const messageInput = document.getElementById("message");
|
||
const sendBtn = document.getElementById("sendBtn");
|
||
const loginButton = document.getElementById("loginButton");
|
||
const userProfile = document.getElementById("userProfile");
|
||
|
||
// Show login overlay and blur app
|
||
loginOverlay.classList.remove("hidden");
|
||
appContainer.classList.add("app-blur");
|
||
|
||
// Disable chat controls
|
||
messageInput.disabled = true;
|
||
sendBtn.disabled = true;
|
||
|
||
// Reset auth UI
|
||
loginButton.classList.remove("hidden");
|
||
userProfile.classList.add("hidden");
|
||
}
|
||
|
||
// Get access token silently
|
||
async function getAccessToken() {
|
||
if (!currentAccount) {
|
||
throw new Error("No user signed in");
|
||
}
|
||
|
||
const request = {
|
||
account: currentAccount,
|
||
scopes: loginRequest.scopes
|
||
};
|
||
|
||
try {
|
||
const response = await msalInstance.acquireTokenSilent(request);
|
||
return response.accessToken;
|
||
} catch (error) {
|
||
console.warn("Silent token acquisition failed, using popup");
|
||
const response = await msalInstance.acquireTokenPopup(request);
|
||
return response.accessToken;
|
||
}
|
||
}
|
||
|
||
const chat = document.getElementById("chat");
|
||
const messageInput = document.getElementById("message");
|
||
const sendBtn = document.getElementById("sendBtn");
|
||
const typingIndicator = document.getElementById("typingIndicator");
|
||
|
||
// Store conversation ID for continuity
|
||
let currentConversationId = null;
|
||
|
||
// Автоматическое изменение размера textarea
|
||
messageInput.addEventListener('input', function() {
|
||
this.style.height = 'auto';
|
||
this.style.height = Math.min(this.scrollHeight, 120) + 'px';
|
||
});
|
||
|
||
// Отправка по Enter (Shift+Enter для новой строки)
|
||
messageInput.addEventListener('keydown', function(e) {
|
||
if (e.key === 'Enter' && !e.shiftKey) {
|
||
e.preventDefault();
|
||
sendMessage();
|
||
}
|
||
});
|
||
|
||
function getCurrentTime() {
|
||
return new Date().toLocaleTimeString('en-US', {
|
||
hour: '2-digit',
|
||
minute: '2-digit'
|
||
});
|
||
}
|
||
|
||
function addMessage(content, isUser = false) {
|
||
const messageDiv = document.createElement('div');
|
||
messageDiv.className = `message ${isUser ? 'user' : 'ai'}`;
|
||
|
||
const avatar = document.createElement('div');
|
||
avatar.className = 'message-avatar';
|
||
avatar.textContent = isUser ? '👤' : '🤖';
|
||
|
||
const messageContent = document.createElement('div');
|
||
messageContent.className = 'message-content';
|
||
|
||
// Apply formatting for AI responses
|
||
if (!isUser) {
|
||
messageContent.innerHTML = formatAIResponse(content);
|
||
} else {
|
||
messageContent.innerHTML = content;
|
||
}
|
||
|
||
const time = document.createElement('div');
|
||
time.className = 'message-time';
|
||
time.textContent = getCurrentTime();
|
||
|
||
messageDiv.appendChild(avatar);
|
||
messageDiv.appendChild(messageContent);
|
||
messageContent.appendChild(time);
|
||
|
||
// Remove welcome message on first message
|
||
const welcomeMsg = chat.querySelector('.welcome-message');
|
||
if (welcomeMsg) {
|
||
welcomeMsg.remove();
|
||
}
|
||
|
||
chat.appendChild(messageDiv);
|
||
chat.scrollTop = chat.scrollHeight;
|
||
|
||
return messageDiv;
|
||
}
|
||
|
||
function showTypingIndicator() {
|
||
typingIndicator.classList.add('show');
|
||
chat.appendChild(typingIndicator);
|
||
chat.scrollTop = chat.scrollHeight;
|
||
}
|
||
|
||
function hideTypingIndicator() {
|
||
typingIndicator.classList.remove('show');
|
||
if (typingIndicator.parentNode) {
|
||
typingIndicator.parentNode.removeChild(typingIndicator);
|
||
}
|
||
}
|
||
|
||
async function sendMessage() {
|
||
// Check if user is authenticated
|
||
if (!currentAccount) {
|
||
alert("Please sign in to use the chat.");
|
||
return;
|
||
}
|
||
|
||
const msg = messageInput.value.trim();
|
||
if (!msg) return;
|
||
|
||
// Block interface
|
||
messageInput.disabled = true;
|
||
sendBtn.disabled = true;
|
||
|
||
// Add user message
|
||
addMessage(msg, true);
|
||
messageInput.value = "";
|
||
messageInput.style.height = 'auto';
|
||
|
||
// Show typing indicator
|
||
showTypingIndicator();
|
||
|
||
try {
|
||
// First try the webhook
|
||
const response = await fetch("https://hook.us1.make.celonis.com/msv2agehbph7w5cvdoeovcb8cclf2rxk", {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
"x-make-apikey": "123qwe"
|
||
},
|
||
body: JSON.stringify({
|
||
message: msg,
|
||
conversationId: currentConversationId,
|
||
userEmail: currentAccount?.username || "unknown",
|
||
userName: currentAccount?.name || "Unknown User"
|
||
}),
|
||
mode: 'cors'
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||
}
|
||
|
||
// Simulate realistic delay
|
||
await new Promise(resolve => setTimeout(resolve, 800 + Math.random() * 1200));
|
||
|
||
// Get response as text first
|
||
const responseText = await response.text();
|
||
console.log('Raw response:', responseText);
|
||
|
||
// Parse JSON and extract reply
|
||
let aiReply;
|
||
let conversationId;
|
||
|
||
try {
|
||
// Clean the response text - remove conversation ID suffix if present
|
||
let cleanResponseText = responseText;
|
||
|
||
// Check if response has conversation ID appended (pattern: }conv_xxxxx)
|
||
const convIdMatch = cleanResponseText.match(/}(conv_[a-f0-9]+)$/);
|
||
if (convIdMatch) {
|
||
// Remove the conversation ID from the end
|
||
cleanResponseText = cleanResponseText.replace(convIdMatch[1], '');
|
||
conversationId = convIdMatch[1];
|
||
console.log('Extracted conversation ID:', conversationId);
|
||
}
|
||
|
||
const data = JSON.parse(cleanResponseText);
|
||
aiReply = data.reply || data.result || data.message || "Sorry, I couldn't process your request.";
|
||
|
||
// Clean up escaped characters and line breaks
|
||
if (aiReply) {
|
||
aiReply = aiReply
|
||
.replace(/\\n/g, '\n') // Convert \n to actual line breaks
|
||
.replace(/\\"/g, '"') // Convert \" to actual quotes
|
||
.replace(/\\\\/g, '\\') // Convert \\ to single backslash
|
||
.trim();
|
||
}
|
||
|
||
console.log('JSON parsed successfully');
|
||
} catch (e) {
|
||
console.log('JSON parse failed, treating as text');
|
||
// If JSON parsing fails, try to extract just the reply content
|
||
let cleanText = responseText;
|
||
|
||
// Try to extract content between "reply": " and the end
|
||
const replyMatch = cleanText.match(/"reply"\s*:\s*"([^"]*(?:\\.[^"]*)*)"/)
|
||
if (replyMatch) {
|
||
aiReply = replyMatch[1]
|
||
.replace(/\\n/g, '\n')
|
||
.replace(/\\"/g, '"')
|
||
.replace(/\\\\/g, '\\')
|
||
.trim();
|
||
} else {
|
||
aiReply = cleanText;
|
||
}
|
||
}
|
||
|
||
// Save conversation ID if present
|
||
if (conversationId) {
|
||
currentConversationId = conversationId;
|
||
console.log('Conversation ID saved:', currentConversationId);
|
||
}
|
||
|
||
// Hide typing indicator
|
||
hideTypingIndicator();
|
||
|
||
// Add AI response with clean text
|
||
addMessage(aiReply);
|
||
console.log('Message added to chat');
|
||
|
||
} catch (error) {
|
||
hideTypingIndicator();
|
||
|
||
// Enhanced error handling with fallback
|
||
console.error('API Error:', error);
|
||
|
||
if (error.name === 'TypeError' && error.message.includes('fetch')) {
|
||
// Network/CORS error - provide fallback response
|
||
addMessage(`🤖 I'm currently having connection issues with my main server. This might be due to CORS restrictions or network problems.
|
||
|
||
**Possible solutions:**
|
||
• Check if the webhook URL is accessible
|
||
• Verify CORS settings on the server
|
||
• Try accessing from a different network
|
||
• Use a CORS proxy for testing
|
||
|
||
For now, I'm running in demo mode. How can I help you today?`, false);
|
||
} else {
|
||
addMessage(`❌ **Error:** ${error.message}
|
||
|
||
*This might be a temporary connection issue. Please try again in a moment.*`, false);
|
||
}
|
||
} finally {
|
||
// Unblock interface
|
||
messageInput.disabled = false;
|
||
sendBtn.disabled = false;
|
||
messageInput.focus();
|
||
}
|
||
}
|
||
|
||
// Focus on input field when page loads
|
||
window.addEventListener('load', () => {
|
||
messageInput.focus();
|
||
});
|
||
|
||
// Format AI response for better readability
|
||
function formatAIResponse(text) {
|
||
return text
|
||
// Handle bullet points with dashes
|
||
.replace(/^- (.+)$/gm, '<div class="bullet-point">• $1</div>')
|
||
// Handle numbered lists
|
||
.replace(/^(\d+\.) (.+)$/gm, '<div class="numbered-point"><span class="number">$1</span> $2</div>')
|
||
// Handle bold text
|
||
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
||
// Handle section headers (lines that end with colon and start new section)
|
||
.replace(/^([A-Z][^:\n]*):$/gm, '<div class="section-header">$1</div>')
|
||
// Handle sub-bullets (lines starting with spaces and dashes)
|
||
.replace(/^\s+- (.+)$/gm, '<div class="sub-bullet">◦ $1</div>')
|
||
// Handle emphasis with underscores
|
||
.replace(/_(.+?)_/g, '<em>$1</em>')
|
||
// Convert line breaks to proper spacing
|
||
.replace(/\n\n/g, '<div class="paragraph-break"></div>')
|
||
.replace(/\n/g, '<br>')
|
||
// Handle special formatting for policy references
|
||
.replace(/\(([^)]+\.pdf|SharePoint|handbook)\)/g, '<span class="policy-ref">($1)</span>');
|
||
}
|
||
</script>
|
||
</body>
|
||
</html> |