Spaces:
Running
Running
| <html lang="ko"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Streaming AI Chat</title> | |
| <script src="https://js.puter.com/v2/"></script> | |
| <script src="/_sdk/element_sdk.js"></script> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> | |
| <style> | |
| body { | |
| box-sizing: border-box; | |
| } | |
| .chat-message { | |
| animation: fadeIn 0.3s ease-in; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .streaming-cursor { | |
| animation: blink 1s infinite; | |
| display: inline-block; | |
| width: 2px; | |
| height: 1.2em; | |
| background-color: #3b82f6; | |
| margin-left: 2px; | |
| } | |
| @keyframes blink { | |
| 0%, 50% { opacity: 1; } | |
| 51%, 100% { opacity: 0; } | |
| } | |
| .chat-container { | |
| height: 100%; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .messages-area { | |
| flex: 1; | |
| overflow-y: auto; | |
| padding: 1rem; | |
| } | |
| .input-area { | |
| border-top: 1px solid #e5e7eb; | |
| padding: 1rem; | |
| background: white; | |
| } | |
| .message-bubble { | |
| max-width: 85%; | |
| word-wrap: break-word; | |
| } | |
| .user-message { | |
| margin-left: auto; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| } | |
| .ai-message { | |
| margin-right: auto; | |
| background: #f8fafc; | |
| border: 1px solid #e2e8f0; | |
| } | |
| .streaming-text { | |
| line-height: 1.6; | |
| } | |
| .model-badge { | |
| display: inline-block; | |
| background: linear-gradient(135deg, #10b981, #059669); | |
| color: white; | |
| padding: 2px 8px; | |
| border-radius: 12px; | |
| font-size: 0.75rem; | |
| font-weight: 500; | |
| margin-bottom: 8px; | |
| } | |
| /* Markdown styling */ | |
| .streaming-text h1, .streaming-text h2, .streaming-text h3 { | |
| font-weight: bold; | |
| margin: 1rem 0 0.5rem 0; | |
| } | |
| .streaming-text h1 { font-size: 1.5rem; } | |
| .streaming-text h2 { font-size: 1.3rem; } | |
| .streaming-text h3 { font-size: 1.1rem; } | |
| .streaming-text code { | |
| background: #f1f5f9; | |
| padding: 2px 4px; | |
| border-radius: 4px; | |
| font-family: 'Courier New', monospace; | |
| font-size: 0.9em; | |
| } | |
| .streaming-text pre { | |
| background: #f8fafc; | |
| border: 1px solid #e2e8f0; | |
| border-radius: 8px; | |
| padding: 1rem; | |
| overflow-x: auto; | |
| margin: 0.5rem 0; | |
| } | |
| .streaming-text pre code { | |
| background: none; | |
| padding: 0; | |
| } | |
| .streaming-text blockquote { | |
| border-left: 4px solid #3b82f6; | |
| padding-left: 1rem; | |
| margin: 0.5rem 0; | |
| font-style: italic; | |
| color: #64748b; | |
| } | |
| .streaming-text ul, .streaming-text ol { | |
| margin: 0.5rem 0; | |
| padding-left: 1.5rem; | |
| } | |
| .streaming-text li { | |
| margin: 0.25rem 0; | |
| } | |
| .streaming-text strong { | |
| font-weight: bold; | |
| } | |
| .streaming-text em { | |
| font-style: italic; | |
| } | |
| .streaming-text a { | |
| color: #3b82f6; | |
| text-decoration: underline; | |
| } | |
| .streaming-text table { | |
| border-collapse: collapse; | |
| width: 100%; | |
| margin: 0.5rem 0; | |
| } | |
| .streaming-text th, .streaming-text td { | |
| border: 1px solid #e2e8f0; | |
| padding: 0.5rem; | |
| text-align: left; | |
| } | |
| .streaming-text th { | |
| background: #f8fafc; | |
| font-weight: bold; | |
| } | |
| </style> | |
| <style>@view-transition { navigation: auto; }</style> | |
| <script src="/_sdk/data_sdk.js" type="text/javascript"></script> | |
| </head> | |
| <body class="h-full bg-gradient-to-br from-blue-50 to-indigo-100"> | |
| <main class="chat-container h-full max-w-5xl mx-auto bg-white shadow-xl"> | |
| <header class="bg-gradient-to-r from-blue-600 to-indigo-600 text-white p-4 shadow-lg"> | |
| <h1 id="chat-title" class="text-2xl font-bold">Streaming AI Chat</h1> | |
| <p class="text-blue-100 mt-1">실시간 스트리밍 응답 + 인터넷 검색, 위키백과, 시간 확인 도구</p> | |
| </header> | |
| <div class="messages-area" id="messages-area"> | |
| <div class="chat-message ai-message message-bubble p-4 rounded-lg mb-4"> | |
| <div class="flex items-start space-x-3"> | |
| <div class="w-8 h-8 bg-gradient-to-r from-green-500 to-emerald-500 rounded-full flex items-center justify-center text-white font-bold text-sm"> | |
| AI | |
| </div> | |
| <div class="flex-1"> | |
| <div class="model-badge"> | |
| GPT-5 | |
| </div> | |
| <p id="welcome-message" class="text-gray-800">안녕하세요! 무엇이든 물어보시면 실시간으로 스트리밍 응답을 보여드릴게요! 🔧 인터넷 검색, 위키백과 검색, 현재 시간 확인 도구를 사용할 수 있습니다.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="input-area"> | |
| <div class="flex items-center justify-between mb-3"><span class="text-sm text-gray-600" id="history-status">대화 기록: 0개</span> <button id="clear-history" class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-lg hover:bg-gray-200 transition-colors duration-200"> 대화 초기화 </button> | |
| </div> | |
| <form id="chat-form" class="flex space-x-3"><input type="text" id="message-input" placeholder="메시지를 입력하세요... (이전 대화를 기억합니다)" class="flex-1 p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" required> <button type="submit" id="send-button" class="px-6 py-3 bg-gradient-to-r from-blue-600 to-indigo-600 text-white rounded-lg hover:from-blue-700 hover:to-indigo-700 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all duration-200 font-medium"> 전송 </button> | |
| </form> | |
| </div> | |
| </main> | |
| <script> | |
| const defaultConfig = { | |
| chat_title: "PIXAL AI Assistant", | |
| welcome_message: "안녕하세요! 저는 PIXAL(Primary Interactive X-ternal Assistant with multi Language)입니다. 개발자 정성윤(6학년 파이썬 프로그래머)이 만들어주었어요! 🔧 인터넷 검색, 위키백과 검색, 현재 시간 확인 도구를 사용할 수 있습니다." | |
| }; | |
| let config = { ...defaultConfig }; | |
| // DOM elements | |
| const messagesArea = document.getElementById('messages-area'); | |
| const messageInput = document.getElementById('message-input'); | |
| const sendButton = document.getElementById('send-button'); | |
| const chatForm = document.getElementById('chat-form'); | |
| // Conversation history | |
| let conversationHistory = []; | |
| // Define tools for the AI to use | |
| const tools = [ | |
| { | |
| type: "function", | |
| function: { | |
| name: "search_internet", | |
| description: "Search the internet for current information, news, or any topic", | |
| parameters: { | |
| type: "object", | |
| properties: { | |
| query: { | |
| type: "string", | |
| description: "The search query to look up on the internet" | |
| } | |
| }, | |
| required: ["query"] | |
| } | |
| } | |
| }, | |
| { | |
| type: "function", | |
| function: { | |
| name: "search_wikipedia", | |
| description: "Search Wikipedia for detailed information about a topic", | |
| parameters: { | |
| type: "object", | |
| properties: { | |
| query: { | |
| type: "string", | |
| description: "The topic to search for on Wikipedia" | |
| } | |
| }, | |
| required: ["query"] | |
| } | |
| } | |
| }, | |
| { | |
| type: "function", | |
| function: { | |
| name: "get_current_time", | |
| description: "Get the current date and time", | |
| parameters: { | |
| type: "object", | |
| properties: { | |
| timezone: { | |
| type: "string", | |
| description: "Optional timezone (e.g., 'Asia/Seoul', 'America/New_York')", | |
| default: "local" | |
| } | |
| } | |
| } | |
| } | |
| } | |
| ]; | |
| // Tool execution functions | |
| async function executeSearchInternet(query) { | |
| try { | |
| // Use DuckDuckGo Instant Answer API (no key required) | |
| const response = await fetch(`https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&no_html=1&skip_disambig=1`); | |
| if (response.ok) { | |
| const data = await response.json(); | |
| let searchResults = `**🌐 인터넷 검색 결과: "${query}"**\n\n`; | |
| // Add abstract if available | |
| if (data.Abstract) { | |
| searchResults += `**요약:**\n${data.Abstract}\n\n`; | |
| } | |
| // Add definition if available | |
| if (data.Definition) { | |
| searchResults += `**정의:**\n${data.Definition}\n\n`; | |
| } | |
| // Add answer if available | |
| if (data.Answer) { | |
| searchResults += `**답변:**\n${data.Answer}\n\n`; | |
| } | |
| // Add related topics | |
| if (data.RelatedTopics && data.RelatedTopics.length > 0) { | |
| searchResults += `**관련 주제:**\n`; | |
| data.RelatedTopics.slice(0, 3).forEach((topic, index) => { | |
| if (topic.Text) { | |
| searchResults += `${index + 1}. ${topic.Text}\n`; | |
| } | |
| }); | |
| searchResults += '\n'; | |
| } | |
| // Add source if available | |
| if (data.AbstractURL) { | |
| searchResults += `**출처:** ${data.AbstractURL}\n`; | |
| } | |
| // If no useful data found, try alternative search | |
| if (!data.Abstract && !data.Definition && !data.Answer && (!data.RelatedTopics || data.RelatedTopics.length === 0)) { | |
| // Fallback to a simple web search simulation with current info | |
| searchResults = `**🌐 인터넷 검색 결과: "${query}"**\n\n`; | |
| searchResults += `검색어 "${query}"에 대한 정보를 찾고 있습니다.\n\n`; | |
| searchResults += `**검색 시간:** ${new Date().toLocaleString('ko-KR')}\n\n`; | |
| searchResults += `**참고:** 더 자세한 정보가 필요하시면 구체적인 질문을 해주세요.`; | |
| } | |
| return searchResults; | |
| } else { | |
| return `인터넷 검색 API 응답 오류: ${response.status}`; | |
| } | |
| } catch (error) { | |
| // Fallback search result | |
| return `**🌐 인터넷 검색 결과: "${query}"**\n\n검색어: ${query}\n검색 시간: ${new Date().toLocaleString('ko-KR')}\n\n네트워크 오류로 인해 실시간 검색 결과를 가져올 수 없습니다. 다시 시도해주세요.\n\n오류: ${error.message}`; | |
| } | |
| } | |
| async function executeSearchWikipedia(query) { | |
| try { | |
| // Use Wikipedia API | |
| const response = await fetch(`https://ko.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(query)}`); | |
| if (response.ok) { | |
| const data = await response.json(); | |
| return `**위키백과 검색 결과: ${data.title}** | |
| ${data.extract} | |
| 더 자세한 정보: ${data.content_urls?.desktop?.page || ''}`; | |
| } else { | |
| // Try English Wikipedia if Korean fails | |
| const enResponse = await fetch(`https://en.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(query)}`); | |
| if (enResponse.ok) { | |
| const enData = await enResponse.json(); | |
| return `**Wikipedia Search Result: ${enData.title}** | |
| ${enData.extract} | |
| More info: ${enData.content_urls?.desktop?.page || ''}`; | |
| } | |
| return `위키백과에서 "${query}"에 대한 정보를 찾을 수 없습니다.`; | |
| } | |
| } catch (error) { | |
| return `위키백과 검색 중 오류가 발생했습니다: ${error.message}`; | |
| } | |
| } | |
| async function executeGetCurrentTime(timezone = 'local') { | |
| try { | |
| const now = new Date(); | |
| let timeString; | |
| if (timezone === 'local') { | |
| timeString = now.toLocaleString('ko-KR', { | |
| year: 'numeric', | |
| month: 'long', | |
| day: 'numeric', | |
| weekday: 'long', | |
| hour: '2-digit', | |
| minute: '2-digit', | |
| second: '2-digit' | |
| }); | |
| } else { | |
| timeString = now.toLocaleString('ko-KR', { | |
| timeZone: timezone, | |
| year: 'numeric', | |
| month: 'long', | |
| day: 'numeric', | |
| weekday: 'long', | |
| hour: '2-digit', | |
| minute: '2-digit', | |
| second: '2-digit' | |
| }); | |
| } | |
| return `**현재 시간** | |
| ${timeString} | |
| 타임존: ${timezone === 'local' ? '로컬 시간' : timezone}`; | |
| } catch (error) { | |
| return `시간 확인 중 오류가 발생했습니다: ${error.message}`; | |
| } | |
| } | |
| // Chat functionality with streaming, memory, and tools | |
| async function sendMessage(userMessage) { | |
| // Add user message to chat | |
| addMessage(userMessage, 'user'); | |
| // Add user message to conversation history | |
| conversationHistory.push({ | |
| role: 'user', | |
| message: userMessage | |
| }); | |
| // Clear input and disable form | |
| messageInput.value = ''; | |
| setLoading(true); | |
| try { | |
| // Create AI message container for streaming | |
| const aiMessageId = createStreamingMessage(); | |
| // Build conversation context string | |
| const conversationContext = buildConversationContext(userMessage); | |
| // Call Puter AI API with tools | |
| const response = await puter.ai.chat(conversationContext, { | |
| model: 'gpt-5-mini', | |
| tools: tools, | |
| stream: false // Disable streaming when using tools | |
| }); | |
| let fullResponse = ''; | |
| // Check if AI wants to use tools | |
| if (response.message && response.message.tool_calls && response.message.tool_calls.length > 0) { | |
| // Execute tool calls | |
| const toolResults = []; | |
| for (const toolCall of response.message.tool_calls) { | |
| const functionName = toolCall.function.name; | |
| const args = JSON.parse(toolCall.function.arguments); | |
| // Show specific tool usage indicator | |
| let toolName = ''; | |
| switch (functionName) { | |
| case 'search_internet': | |
| toolName = '🌐 인터넷 검색'; | |
| break; | |
| case 'search_wikipedia': | |
| toolName = '📚 위키백과 검색'; | |
| break; | |
| case 'get_current_time': | |
| toolName = '⏰ 시간 확인'; | |
| break; | |
| default: | |
| toolName = '🔧 도구'; | |
| } | |
| updateStreamingMessage(aiMessageId, `${toolName} 도구를 사용하고 있습니다...\n쿼리: ${args.query || args.timezone || '현재 시간'}`, true); | |
| let result; | |
| switch (functionName) { | |
| case 'search_internet': | |
| result = await executeSearchInternet(args.query); | |
| break; | |
| case 'search_wikipedia': | |
| result = await executeSearchWikipedia(args.query); | |
| break; | |
| case 'get_current_time': | |
| result = await executeGetCurrentTime(args.timezone); | |
| break; | |
| default: | |
| result = `알 수 없는 도구: ${functionName}`; | |
| } | |
| toolResults.push({ | |
| tool: functionName, | |
| toolName: toolName, | |
| query: args.query || args.timezone || 'N/A', | |
| result: result | |
| }); | |
| } | |
| // Get AI's final response with tool results | |
| const toolResultsText = toolResults.map(tr => | |
| `사용된 도구: ${tr.toolName}\n쿼리: ${tr.query}\n결과: ${tr.result}` | |
| ).join('\n\n'); | |
| const finalContext = `${conversationContext}\n\n도구 실행 결과:\n${toolResultsText}\n\n위 도구 결과를 바탕으로 사용자에게 도움이 되는 답변을 해주세요. 답변 시작 부분에 어떤 도구를 사용했는지 간단히 언급해주세요.`; | |
| const finalResponse = await puter.ai.chat(finalContext, { | |
| model: 'gpt-5', | |
| stream: true | |
| }); | |
| // Stream the final response | |
| for await (const part of finalResponse) { | |
| if (part?.text) { | |
| fullResponse += part.text; | |
| updateStreamingMessage(aiMessageId, fullResponse, true); | |
| } | |
| } | |
| } else { | |
| // No tools needed, stream normally | |
| const streamResponse = await puter.ai.chat(conversationContext, { | |
| model: 'gpt-5-mini', | |
| stream: true | |
| }); | |
| for await (const part of streamResponse) { | |
| if (part?.text) { | |
| fullResponse += part.text; | |
| updateStreamingMessage(aiMessageId, fullResponse, true); | |
| } | |
| } | |
| } | |
| // Remove cursor when streaming is complete | |
| updateStreamingMessage(aiMessageId, fullResponse, false); | |
| // Add AI response to conversation history | |
| conversationHistory.push({ | |
| role: 'pixal', | |
| message: fullResponse | |
| }); | |
| // Update history status | |
| updateHistoryStatus(); | |
| } catch (error) { | |
| const errorMessage = '죄송합니다. 오류가 발생했습니다. 다시 시도해주세요.'; | |
| addMessage(errorMessage, 'ai', true); | |
| console.error('Streaming chat error:', error); | |
| } finally { | |
| setLoading(false); | |
| } | |
| } | |
| function buildConversationContext(currentMessage) { | |
| let context = ''; | |
| // Add system message | |
| context += '시스템 메시지: 너는 PIXAL(Primary Interactive X-ternal Assistant with multi Language)이야. 너의 개발자는 정성윤 이라는 6학년 파이썬 프로그래머야. 이 메시지는 사용자가 보낸것이 아닙니다.\n\n'; | |
| // Add previous conversation history | |
| if (conversationHistory.length > 0) { | |
| context += '이전 대화:\n'; | |
| conversationHistory.forEach(entry => { | |
| if (entry.role === 'user') { | |
| context += `사용자: ${entry.message}\n`; | |
| } else { | |
| context += `PIXAL: ${entry.message}\n`; | |
| } | |
| }); | |
| context += '\n현재 질문:\n'; | |
| } | |
| context += `사용자: ${currentMessage}`; | |
| return context; | |
| } | |
| function addMessage(content, sender, isError = false) { | |
| const messageDiv = document.createElement('div'); | |
| messageDiv.className = `chat-message ${sender}-message message-bubble p-4 rounded-lg mb-4`; | |
| if (sender === 'user') { | |
| messageDiv.innerHTML = ` | |
| <div class="flex items-start space-x-3 justify-end"> | |
| <div class="flex-1 text-right"> | |
| <p class="text-white">${escapeHtml(content)}</p> | |
| </div> | |
| <div class="w-8 h-8 bg-gradient-to-r from-purple-500 to-pink-500 rounded-full flex items-center justify-center text-white font-bold text-sm"> | |
| U | |
| </div> | |
| </div> | |
| `; | |
| } else { | |
| messageDiv.innerHTML = ` | |
| <div class="flex items-start space-x-3"> | |
| <div class="w-8 h-8 bg-gradient-to-r from-green-500 to-emerald-500 rounded-full flex items-center justify-center text-white font-bold text-sm"> | |
| AI | |
| </div> | |
| <div class="flex-1"> | |
| <div class="model-badge">PIXAL • GPT-5</div> | |
| <div class="${isError ? 'text-red-600' : 'text-gray-800'} streaming-text">${formatAIResponse(content)}</div> | |
| </div> | |
| </div> | |
| `; | |
| } | |
| messagesArea.appendChild(messageDiv); | |
| scrollToBottom(); | |
| } | |
| function createStreamingMessage() { | |
| const messageDiv = document.createElement('div'); | |
| const messageId = 'streaming-' + Date.now(); | |
| messageDiv.id = messageId; | |
| messageDiv.className = 'chat-message ai-message message-bubble p-4 rounded-lg mb-4'; | |
| messageDiv.innerHTML = ` | |
| <div class="flex items-start space-x-3"> | |
| <div class="w-8 h-8 bg-gradient-to-r from-green-500 to-emerald-500 rounded-full flex items-center justify-center text-white font-bold text-sm"> | |
| AI | |
| </div> | |
| <div class="flex-1"> | |
| <div class="model-badge">PIXAL • Streaming</div> | |
| <div class="text-gray-800 streaming-text" id="${messageId}-content"> | |
| <span class="streaming-cursor"></span> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| messagesArea.appendChild(messageDiv); | |
| scrollToBottom(); | |
| return messageId; | |
| } | |
| function updateStreamingMessage(messageId, content, isStreaming) { | |
| const contentElement = document.getElementById(messageId + '-content'); | |
| const badgeElement = document.querySelector(`#${messageId} .model-badge`); | |
| if (contentElement) { | |
| const formattedContent = formatStreamingResponse(content); | |
| if (isStreaming) { | |
| contentElement.innerHTML = formattedContent + '<span class="streaming-cursor"></span>'; | |
| badgeElement.textContent = 'PIXAL • Streaming'; | |
| } else { | |
| contentElement.innerHTML = formattedContent; | |
| badgeElement.textContent = 'PIXAL • GPT-5'; | |
| } | |
| scrollToBottom(); | |
| } | |
| } | |
| function setLoading(loading) { | |
| sendButton.disabled = loading; | |
| messageInput.disabled = loading; | |
| if (loading) { | |
| sendButton.textContent = '응답 중...'; | |
| sendButton.classList.add('opacity-50', 'cursor-not-allowed'); | |
| } else { | |
| sendButton.textContent = '전송'; | |
| sendButton.classList.remove('opacity-50', 'cursor-not-allowed'); | |
| } | |
| } | |
| function scrollToBottom() { | |
| messagesArea.scrollTop = messagesArea.scrollHeight; | |
| } | |
| function escapeHtml(text) { | |
| const div = document.createElement('div'); | |
| div.textContent = text; | |
| return div.innerHTML; | |
| } | |
| function formatAIResponse(content) { | |
| // Use marked.js to parse markdown | |
| if (typeof marked !== 'undefined') { | |
| return marked.parse(content); | |
| } | |
| // Fallback to simple formatting | |
| return escapeHtml(content) | |
| .replace(/\n\n/g, '</p><p class="mt-3">') | |
| .replace(/\n/g, '<br>') | |
| .replace(/^/, '<p>') | |
| .replace(/$/, '</p>'); | |
| } | |
| function formatStreamingResponse(content) { | |
| // Use marked.js to parse markdown for streaming content | |
| if (typeof marked !== 'undefined') { | |
| return marked.parse(content); | |
| } | |
| // Fallback to simple formatting | |
| return escapeHtml(content) | |
| .replace(/\n\n/g, '</p><p class="mt-3">') | |
| .replace(/\n/g, '<br>') | |
| .replace(/^/, '<p>') | |
| .replace(/$/, '</p>'); | |
| } | |
| function updateHistoryStatus() { | |
| const historyCount = Math.floor(conversationHistory.length / 2); | |
| document.getElementById('history-status').textContent = `대화 기록: ${historyCount}개`; | |
| } | |
| function clearConversationHistory() { | |
| conversationHistory = []; | |
| updateHistoryStatus(); | |
| // Show confirmation message | |
| const confirmDiv = document.createElement('div'); | |
| confirmDiv.className = 'chat-message ai-message message-bubble p-4 rounded-lg mb-4'; | |
| confirmDiv.innerHTML = ` | |
| <div class="flex items-start space-x-3"> | |
| <div class="w-8 h-8 bg-gradient-to-r from-green-500 to-emerald-500 rounded-full flex items-center justify-center text-white font-bold text-sm"> | |
| AI | |
| </div> | |
| <div class="flex-1"> | |
| <div class="model-badge">System</div> | |
| <p class="text-gray-600 italic">대화 기록이 초기화되었습니다. 새로운 대화를 시작해주세요!</p> | |
| </div> | |
| </div> | |
| `; | |
| messagesArea.appendChild(confirmDiv); | |
| scrollToBottom(); | |
| } | |
| // Event listeners | |
| chatForm.addEventListener('submit', (e) => { | |
| e.preventDefault(); | |
| const message = messageInput.value.trim(); | |
| if (message && !sendButton.disabled) { | |
| sendMessage(message); | |
| } | |
| }); | |
| document.getElementById('clear-history').addEventListener('click', () => { | |
| clearConversationHistory(); | |
| }); | |
| // Focus input on load | |
| messageInput.focus(); | |
| // Element SDK implementation | |
| async function onConfigChange(newConfig) { | |
| config = { ...config, ...newConfig }; | |
| // Update UI elements | |
| document.getElementById('chat-title').textContent = config.chat_title || defaultConfig.chat_title; | |
| document.getElementById('welcome-message').textContent = config.welcome_message || defaultConfig.welcome_message; | |
| } | |
| function mapToCapabilities(config) { | |
| return { | |
| recolorables: [], | |
| borderables: [], | |
| fontEditable: undefined, | |
| fontSizeable: undefined | |
| }; | |
| } | |
| function mapToEditPanelValues(config) { | |
| return new Map([ | |
| ['chat_title', config.chat_title || defaultConfig.chat_title], | |
| ['welcome_message', config.welcome_message || defaultConfig.welcome_message] | |
| ]); | |
| } | |
| // Initialize Element SDK | |
| if (window.elementSdk) { | |
| window.elementSdk.init({ | |
| defaultConfig, | |
| onConfigChange, | |
| mapToCapabilities, | |
| mapToEditPanelValues | |
| }); | |
| } | |
| </script> | |
| <script>(function(){function c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'9955ea6b9063aa32',t:'MTc2MTYwNzEzOS4wMDAwMDA='};var a=document.createElement('script');a.nonce='';a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body> | |
| </html> |