-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbundle3.min.js
More file actions
156 lines (148 loc) · 9.63 KB
/
bundle3.min.js
File metadata and controls
156 lines (148 loc) · 9.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
class LangflowChat extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: "open" })
this.debug = false
}
log(...args) {
if (this.debug) {
console.log("[LangflowChat]", ...args)
}
}
async validateConfig(apiKey, hostUrl, flowId) {
this.log("Validating config:", { apiKey, hostUrl, flowId })
if (!apiKey || !hostUrl || !flowId) {
const missing = []
!apiKey && missing.push("apiKey")
!hostUrl && missing.push("hostUrl")
!flowId && missing.push("flowId")
throw new Error(`Missing required configuration: ${missing.join(", ")}`)
}
try {
const response = await fetch(`${hostUrl}/api/v1/health`, {
method: "GET",
headers: {
Accept: "application/json",
"api-key": apiKey,
},
})
this.log("Validation response:", response.status)
if (response.status === 403) {
throw new Error("Invalid API key or insufficient permissions")
}
if (!response.ok) {
throw new Error(`API validation failed: ${response.status}`)
}
} catch (error) {
this.log("Validation error:", error)
throw error
}
}
async connectedCallback() {
const windowTitle = this.getAttribute("window_title") || "Chat Support"
const flowId = this.getAttribute("flow_id")
const hostUrl = this.getAttribute("host_url")
const apiKey = this.getAttribute("api_key")
this.debug = this.getAttribute("debug") === "true"
try {
await this.validateConfig(apiKey, hostUrl, flowId)
} catch (error) {
console.error("Configuration Error:", error)
return this.renderError(error.message)
}
const style = document.createElement("style")
style.textContent = `:host{--primary:#7c3aed;--primary-foreground:#fff;--muted:#f3f4f6;--muted-foreground:#6b7280;--border:#e5e7eb}:host(.dark){--primary:#7c3aed;--primary-foreground:#fff;--muted:#1f2937;--muted-foreground:#9ca3af;--border:#374151}.widget-container{position:fixed;bottom:1rem;right:1rem;z-index:50;font-family:system-ui,sans-serif}.chat-card{width:380px;height:600px;background:#fff;border:1px solid var(--border);border-radius:.5rem;box-shadow:0 20px 25px -5px rgba(0,0,0,.1);display:flex;flex-direction:column}.dark .chat-card{background:#111827;color:#fff}.chat-header{padding:1rem;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between}.header-title{display:flex;align-items:center;gap:.5rem;font-weight:600}.header-actions{display:flex;align-items:center;gap:.5rem}.chat-content{flex:1;overflow-y:auto;padding:1rem;display:flex;flex-direction:column;gap:1rem}.message{display:flex;align-items:start;gap:.5rem}.message.user{flex-direction:row-reverse}.message-bubble{max-width:80%;padding:.5rem 1rem;border-radius:.5rem}.message.assistant .message-bubble{background:var(--muted);color:var(--muted-foreground)}.message.user .message-bubble{background:var(--primary);color:var(--primary-foreground)}.avatar{width:2rem;height:2rem;border-radius:50%;overflow:hidden}.avatar img{width:100%;height:100%;object-fit:cover}.chat-footer{padding:1rem;border-top:1px solid var(--border)}.chat-form{display:flex;gap:.5rem}.chat-input{flex:1;padding:.5rem;border:1px solid var(--border);border-radius:.375rem;background:transparent;color:inherit}.dark .chat-input{border-color:var(--border)}.chat-input:focus{outline:2px solid var(--primary);outline-offset:-1px}.btn{display:inline-flex;align-items:center;justify-content:center;padding:.5rem;border:none;border-radius:.375rem;background:var(--primary);color:var(--primary-foreground);cursor:pointer}.btn:hover{opacity:.9}.btn.icon{padding:.5rem}.toggle-btn{position:fixed;bottom:1rem;right:1rem;width:3rem;height:3rem;border-radius:9999px;background:var(--primary);color:var(--primary-foreground);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center}.theme-toggle{background:transparent;border:none;color:inherit;cursor:pointer;padding:.5rem;border-radius:.375rem}.theme-toggle:hover{background:var(--muted)}.error-message{position:fixed;bottom:1rem;right:1rem;background:#fee2e2;border:1px solid #ef4444;color:#dc2626;padding:1rem;border-radius:.5rem;max-width:300px;z-index:50}`
this.shadowRoot.appendChild(style)
const widgetContainer = document.createElement("div")
widgetContainer.className = "widget-container"
widgetContainer.innerHTML = `<div id="chat-window" style="display:none" class="chat-card"><div class="chat-header"><div class="header-title"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>${windowTitle}</div><div class="header-actions"><button id="theme-toggle" class="theme-toggle"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg></button><button id="close-chat" class="theme-toggle"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12"/></svg></button></div></div><div id="chat-content" class="chat-content"></div><div class="chat-footer"><form id="chat-form" class="chat-form"><input type="text" id="message-input" class="chat-input" placeholder="Type your message..."><button type="submit" class="btn icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/></svg></button></form></div></div><button id="toggle-chat" class="toggle-btn"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></button>`
this.shadowRoot.appendChild(widgetContainer)
const chatWindow = this.shadowRoot.getElementById("chat-window")
const toggleChat = this.shadowRoot.getElementById("toggle-chat")
const closeChat = this.shadowRoot.getElementById("close-chat")
const chatContent = this.shadowRoot.getElementById("chat-content")
const chatForm = this.shadowRoot.getElementById("chat-form")
const messageInput = this.shadowRoot.getElementById("message-input")
const themeToggle = this.shadowRoot.getElementById("theme-toggle")
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)")
const toggleDarkMode = (dark) => {
this.classList.toggle("dark", dark)
}
toggleDarkMode(prefersDark.matches)
prefersDark.addEventListener("change", (e) => toggleDarkMode(e.matches))
themeToggle.addEventListener("click", () => {
this.classList.toggle("dark")
})
toggleChat.addEventListener("click", () => {
chatWindow.style.display = "flex"
toggleChat.style.display = "none"
})
closeChat.addEventListener("click", () => {
chatWindow.style.display = "none"
toggleChat.style.display = "flex"
})
chatForm.addEventListener("submit", async (e) => {
e.preventDefault()
const message = messageInput.value.trim()
if (!message) return
appendMessage("user", message)
messageInput.value = ""
try {
const url = new URL(`${hostUrl}/api/v1/process/${flowId}`)
this.log("Sending request to:", url.toString())
this.log("Using API key:", apiKey)
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"api-key": apiKey,
},
body: JSON.stringify({
input: { question: message },
conversation_id: Date.now().toString(),
}),
})
this.log("Response status:", response.status)
if (response.status === 403) {
const errorData = await response.json()
this.log("Error response:", errorData)
throw new Error(`Authentication failed: ${errorData.detail || "Invalid API key format or permissions"}`)
}
if (!response.ok) {
const errorText = await response.text()
this.log("Error response text:", errorText)
throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`)
}
const data = await response.json()
this.log("Response data:", data)
if (!data.data || !data.data.length) {
throw new Error("Invalid response format")
}
appendMessage("assistant", data.data[0].content)
} catch (error) {
console.error("Chat API Error:", error)
appendMessage("assistant", `Error: ${error.message}`)
}
})
const appendMessage = (sender, message) => {
const messageElement = document.createElement("div")
messageElement.className = `message ${sender}`
let messageContent = `<div class="message-bubble">${message}</div>`
if (sender === "assistant") {
messageContent = `<div class="avatar"><img src="https://hebbkx1anhila5yf.public.blob.vercel-storage.com/399-rmQgplK2ef7TvviORQuRol3msTsG4E.png" alt="AI Assistant"></div>${messageContent}`
}
messageElement.innerHTML = messageContent
chatContent.appendChild(messageElement)
chatContent.scrollTop = chatContent.scrollHeight
}
appendMessage("assistant", "Hello! How can I help you today?")
}
renderError(message) {
const errorElement = document.createElement("div")
errorElement.className = "error-message"
errorElement.textContent = `Configuration Error: ${message}`
this.shadowRoot.appendChild(errorElement)
}
}
customElements.define("langflow-chat", LangflowChat)