peterpeter8585 commited on
Commit
ec15833
·
verified ·
1 Parent(s): 8932884

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +760 -18
index.html CHANGED
@@ -1,19 +1,761 @@
1
  <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <!doctype html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Streaming AI Chat</title>
7
+ <script src="https://js.puter.com/v2/"></script>
8
+ <script src="/_sdk/element_sdk.js"></script>
9
+ <script src="https://cdn.tailwindcss.com"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
11
+ <style>
12
+ body {
13
+ box-sizing: border-box;
14
+ }
15
+
16
+ .chat-message {
17
+ animation: fadeIn 0.3s ease-in;
18
+ }
19
+
20
+ @keyframes fadeIn {
21
+ from { opacity: 0; transform: translateY(10px); }
22
+ to { opacity: 1; transform: translateY(0); }
23
+ }
24
+
25
+ .streaming-cursor {
26
+ animation: blink 1s infinite;
27
+ display: inline-block;
28
+ width: 2px;
29
+ height: 1.2em;
30
+ background-color: #3b82f6;
31
+ margin-left: 2px;
32
+ }
33
+
34
+ @keyframes blink {
35
+ 0%, 50% { opacity: 1; }
36
+ 51%, 100% { opacity: 0; }
37
+ }
38
+
39
+ .chat-container {
40
+ height: 100%;
41
+ display: flex;
42
+ flex-direction: column;
43
+ }
44
+
45
+ .messages-area {
46
+ flex: 1;
47
+ overflow-y: auto;
48
+ padding: 1rem;
49
+ }
50
+
51
+ .input-area {
52
+ border-top: 1px solid #e5e7eb;
53
+ padding: 1rem;
54
+ background: white;
55
+ }
56
+
57
+ .message-bubble {
58
+ max-width: 85%;
59
+ word-wrap: break-word;
60
+ }
61
+
62
+ .user-message {
63
+ margin-left: auto;
64
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
65
+ }
66
+
67
+ .ai-message {
68
+ margin-right: auto;
69
+ background: #f8fafc;
70
+ border: 1px solid #e2e8f0;
71
+ }
72
+
73
+ .streaming-text {
74
+ line-height: 1.6;
75
+ }
76
+
77
+ .model-badge {
78
+ display: inline-block;
79
+ background: linear-gradient(135deg, #10b981, #059669);
80
+ color: white;
81
+ padding: 2px 8px;
82
+ border-radius: 12px;
83
+ font-size: 0.75rem;
84
+ font-weight: 500;
85
+ margin-bottom: 8px;
86
+ }
87
+
88
+ /* Markdown styling */
89
+ .streaming-text h1, .streaming-text h2, .streaming-text h3 {
90
+ font-weight: bold;
91
+ margin: 1rem 0 0.5rem 0;
92
+ }
93
+
94
+ .streaming-text h1 { font-size: 1.5rem; }
95
+ .streaming-text h2 { font-size: 1.3rem; }
96
+ .streaming-text h3 { font-size: 1.1rem; }
97
+
98
+ .streaming-text code {
99
+ background: #f1f5f9;
100
+ padding: 2px 4px;
101
+ border-radius: 4px;
102
+ font-family: 'Courier New', monospace;
103
+ font-size: 0.9em;
104
+ }
105
+
106
+ .streaming-text pre {
107
+ background: #f8fafc;
108
+ border: 1px solid #e2e8f0;
109
+ border-radius: 8px;
110
+ padding: 1rem;
111
+ overflow-x: auto;
112
+ margin: 0.5rem 0;
113
+ }
114
+
115
+ .streaming-text pre code {
116
+ background: none;
117
+ padding: 0;
118
+ }
119
+
120
+ .streaming-text blockquote {
121
+ border-left: 4px solid #3b82f6;
122
+ padding-left: 1rem;
123
+ margin: 0.5rem 0;
124
+ font-style: italic;
125
+ color: #64748b;
126
+ }
127
+
128
+ .streaming-text ul, .streaming-text ol {
129
+ margin: 0.5rem 0;
130
+ padding-left: 1.5rem;
131
+ }
132
+
133
+ .streaming-text li {
134
+ margin: 0.25rem 0;
135
+ }
136
+
137
+ .streaming-text strong {
138
+ font-weight: bold;
139
+ }
140
+
141
+ .streaming-text em {
142
+ font-style: italic;
143
+ }
144
+
145
+ .streaming-text a {
146
+ color: #3b82f6;
147
+ text-decoration: underline;
148
+ }
149
+
150
+ .streaming-text table {
151
+ border-collapse: collapse;
152
+ width: 100%;
153
+ margin: 0.5rem 0;
154
+ }
155
+
156
+ .streaming-text th, .streaming-text td {
157
+ border: 1px solid #e2e8f0;
158
+ padding: 0.5rem;
159
+ text-align: left;
160
+ }
161
+
162
+ .streaming-text th {
163
+ background: #f8fafc;
164
+ font-weight: bold;
165
+ }
166
+ </style>
167
+ <style>@view-transition { navigation: auto; }</style>
168
+ <script src="/_sdk/data_sdk.js" type="text/javascript"></script>
169
+ </head>
170
+ <body class="h-full bg-gradient-to-br from-blue-50 to-indigo-100">
171
+ <main class="chat-container h-full max-w-5xl mx-auto bg-white shadow-xl">
172
+ <header class="bg-gradient-to-r from-blue-600 to-indigo-600 text-white p-4 shadow-lg">
173
+ <h1 id="chat-title" class="text-2xl font-bold">Streaming AI Chat</h1>
174
+ <p class="text-blue-100 mt-1">실시간 스트리밍 응답 + 인터넷 검색, 위키백과, 시간 확인 도구</p>
175
+ </header>
176
+ <div class="messages-area" id="messages-area">
177
+ <div class="chat-message ai-message message-bubble p-4 rounded-lg mb-4">
178
+ <div class="flex items-start space-x-3">
179
+ <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">
180
+ AI
181
+ </div>
182
+ <div class="flex-1">
183
+ <div class="model-badge">
184
+ GPT-5
185
+ </div>
186
+ <p id="welcome-message" class="text-gray-800">안녕하세요! 무엇이든 물어보시면 실시간으로 스트리밍 응답을 보여드릴게요! 🔧 인터넷 검색, 위키백과 검색, 현재 시간 확인 도구를 사용할 수 있습니다.</p>
187
+ </div>
188
+ </div>
189
+ </div>
190
+ </div>
191
+ <div class="input-area">
192
+ <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>
193
+ </div>
194
+ <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>
195
+ </form>
196
+ </div>
197
+ </main>
198
+ <script>
199
+ const defaultConfig = {
200
+ chat_title: "PIXAL AI Assistant",
201
+ welcome_message: "안녕하세요! 저는 PIXAL(Primary Interactive X-ternal Assistant with multi Language)입니다. 개발자 정성윤(6학년 파이썬 프로그래머)이 만들어주었어요! 🔧 인터넷 검색, 위키백과 검색, 현재 시간 확인 도구를 사용할 수 있습니다."
202
+ };
203
+
204
+ let config = { ...defaultConfig };
205
+
206
+ // DOM elements
207
+ const messagesArea = document.getElementById('messages-area');
208
+ const messageInput = document.getElementById('message-input');
209
+ const sendButton = document.getElementById('send-button');
210
+ const chatForm = document.getElementById('chat-form');
211
+
212
+ // Conversation history
213
+ let conversationHistory = [];
214
+
215
+ // Define tools for the AI to use
216
+ const tools = [
217
+ {
218
+ type: "function",
219
+ function: {
220
+ name: "search_internet",
221
+ description: "Search the internet for current information, news, or any topic",
222
+ parameters: {
223
+ type: "object",
224
+ properties: {
225
+ query: {
226
+ type: "string",
227
+ description: "The search query to look up on the internet"
228
+ }
229
+ },
230
+ required: ["query"]
231
+ }
232
+ }
233
+ },
234
+ {
235
+ type: "function",
236
+ function: {
237
+ name: "search_wikipedia",
238
+ description: "Search Wikipedia for detailed information about a topic",
239
+ parameters: {
240
+ type: "object",
241
+ properties: {
242
+ query: {
243
+ type: "string",
244
+ description: "The topic to search for on Wikipedia"
245
+ }
246
+ },
247
+ required: ["query"]
248
+ }
249
+ }
250
+ },
251
+ {
252
+ type: "function",
253
+ function: {
254
+ name: "get_current_time",
255
+ description: "Get the current date and time",
256
+ parameters: {
257
+ type: "object",
258
+ properties: {
259
+ timezone: {
260
+ type: "string",
261
+ description: "Optional timezone (e.g., 'Asia/Seoul', 'America/New_York')",
262
+ default: "local"
263
+ }
264
+ }
265
+ }
266
+ }
267
+ }
268
+ ];
269
+
270
+ // Tool execution functions
271
+ async function executeSearchInternet(query) {
272
+ try {
273
+ // Use DuckDuckGo Instant Answer API (no key required)
274
+ const response = await fetch(`https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&no_html=1&skip_disambig=1`);
275
+
276
+ if (response.ok) {
277
+ const data = await response.json();
278
+
279
+ let searchResults = `**🌐 인터넷 검색 결과: "${query}"**\n\n`;
280
+
281
+ // Add abstract if available
282
+ if (data.Abstract) {
283
+ searchResults += `**요약:**\n${data.Abstract}\n\n`;
284
+ }
285
+
286
+ // Add definition if available
287
+ if (data.Definition) {
288
+ searchResults += `**정의:**\n${data.Definition}\n\n`;
289
+ }
290
+
291
+ // Add answer if available
292
+ if (data.Answer) {
293
+ searchResults += `**답변:**\n${data.Answer}\n\n`;
294
+ }
295
+
296
+ // Add related topics
297
+ if (data.RelatedTopics && data.RelatedTopics.length > 0) {
298
+ searchResults += `**관련 주제:**\n`;
299
+ data.RelatedTopics.slice(0, 3).forEach((topic, index) => {
300
+ if (topic.Text) {
301
+ searchResults += `${index + 1}. ${topic.Text}\n`;
302
+ }
303
+ });
304
+ searchResults += '\n';
305
+ }
306
+
307
+ // Add source if available
308
+ if (data.AbstractURL) {
309
+ searchResults += `**출처:** ${data.AbstractURL}\n`;
310
+ }
311
+
312
+ // If no useful data found, try alternative search
313
+ if (!data.Abstract && !data.Definition && !data.Answer && (!data.RelatedTopics || data.RelatedTopics.length === 0)) {
314
+ // Fallback to a simple web search simulation with current info
315
+ searchResults = `**🌐 인터넷 검색 결과: "${query}"**\n\n`;
316
+ searchResults += `검색어 "${query}"에 대한 정보를 찾고 있습니다.\n\n`;
317
+ searchResults += `**검색 시간:** ${new Date().toLocaleString('ko-KR')}\n\n`;
318
+ searchResults += `**참고:** 더 자세한 정보가 필요하시면 구체적인 질문을 해주세요.`;
319
+ }
320
+
321
+ return searchResults;
322
+ } else {
323
+ return `인터넷 검색 API 응답 오류: ${response.status}`;
324
+ }
325
+ } catch (error) {
326
+ // Fallback search result
327
+ return `**🌐 인터넷 검색 결과: "${query}"**\n\n검색어: ${query}\n검색 시간: ${new Date().toLocaleString('ko-KR')}\n\n네트워크 오류로 인해 실시간 검색 결과를 가져올 수 없습니다. 다시 시도해주세요.\n\n오류: ${error.message}`;
328
+ }
329
+ }
330
+
331
+ async function executeSearchWikipedia(query) {
332
+ try {
333
+ // Use Wikipedia API
334
+ const response = await fetch(`https://ko.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(query)}`);
335
+
336
+ if (response.ok) {
337
+ const data = await response.json();
338
+ return `**위키백과 검색 결과: ${data.title}**
339
+
340
+ ${data.extract}
341
+
342
+ 더 자세한 정보: ${data.content_urls?.desktop?.page || ''}`;
343
+ } else {
344
+ // Try English Wikipedia if Korean fails
345
+ const enResponse = await fetch(`https://en.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(query)}`);
346
+ if (enResponse.ok) {
347
+ const enData = await enResponse.json();
348
+ return `**Wikipedia Search Result: ${enData.title}**
349
+
350
+ ${enData.extract}
351
+
352
+ More info: ${enData.content_urls?.desktop?.page || ''}`;
353
+ }
354
+ return `위키백과에서 "${query}"에 대한 정보를 찾을 수 없습니다.`;
355
+ }
356
+ } catch (error) {
357
+ return `위키백과 검색 중 오류가 발생했습니다: ${error.message}`;
358
+ }
359
+ }
360
+
361
+ async function executeGetCurrentTime(timezone = 'local') {
362
+ try {
363
+ const now = new Date();
364
+ let timeString;
365
+
366
+ if (timezone === 'local') {
367
+ timeString = now.toLocaleString('ko-KR', {
368
+ year: 'numeric',
369
+ month: 'long',
370
+ day: 'numeric',
371
+ weekday: 'long',
372
+ hour: '2-digit',
373
+ minute: '2-digit',
374
+ second: '2-digit'
375
+ });
376
+ } else {
377
+ timeString = now.toLocaleString('ko-KR', {
378
+ timeZone: timezone,
379
+ year: 'numeric',
380
+ month: 'long',
381
+ day: 'numeric',
382
+ weekday: 'long',
383
+ hour: '2-digit',
384
+ minute: '2-digit',
385
+ second: '2-digit'
386
+ });
387
+ }
388
+
389
+ return `**현재 시간**
390
+
391
+ ${timeString}
392
+
393
+ 타임존: ${timezone === 'local' ? '로컬 시간' : timezone}`;
394
+ } catch (error) {
395
+ return `시간 확인 중 오류가 발생했습니다: ${error.message}`;
396
+ }
397
+ }
398
+
399
+ // Chat functionality with streaming, memory, and tools
400
+ async function sendMessage(userMessage) {
401
+ // Add user message to chat
402
+ addMessage(userMessage, 'user');
403
+
404
+ // Add user message to conversation history
405
+ conversationHistory.push({
406
+ role: 'user',
407
+ message: userMessage
408
+ });
409
+
410
+ // Clear input and disable form
411
+ messageInput.value = '';
412
+ setLoading(true);
413
+
414
+ try {
415
+ // Create AI message container for streaming
416
+ const aiMessageId = createStreamingMessage();
417
+
418
+ // Build conversation context string
419
+ const conversationContext = buildConversationContext(userMessage);
420
+
421
+ // Call Puter AI API with tools
422
+ const response = await puter.ai.chat(conversationContext, {
423
+ model: 'gpt-5-mini',
424
+ tools: tools,
425
+ stream: false // Disable streaming when using tools
426
+ });
427
+
428
+ let fullResponse = '';
429
+
430
+ // Check if AI wants to use tools
431
+ if (response.message && response.message.tool_calls && response.message.tool_calls.length > 0) {
432
+ // Execute tool calls
433
+ const toolResults = [];
434
+ for (const toolCall of response.message.tool_calls) {
435
+ const functionName = toolCall.function.name;
436
+ const args = JSON.parse(toolCall.function.arguments);
437
+
438
+ // Show specific tool usage indicator
439
+ let toolName = '';
440
+ switch (functionName) {
441
+ case 'search_internet':
442
+ toolName = '🌐 인터넷 검색';
443
+ break;
444
+ case 'search_wikipedia':
445
+ toolName = '📚 위키백과 검색';
446
+ break;
447
+ case 'get_current_time':
448
+ toolName = '⏰ 시간 확인';
449
+ break;
450
+ default:
451
+ toolName = '🔧 도구';
452
+ }
453
+
454
+ updateStreamingMessage(aiMessageId, `${toolName} 도구를 사용하고 있습니다...\n쿼리: ${args.query || args.timezone || '현재 시간'}`, true);
455
+
456
+ let result;
457
+ switch (functionName) {
458
+ case 'search_internet':
459
+ result = await executeSearchInternet(args.query);
460
+ break;
461
+ case 'search_wikipedia':
462
+ result = await executeSearchWikipedia(args.query);
463
+ break;
464
+ case 'get_current_time':
465
+ result = await executeGetCurrentTime(args.timezone);
466
+ break;
467
+ default:
468
+ result = `알 수 없는 도구: ${functionName}`;
469
+ }
470
+
471
+ toolResults.push({
472
+ tool: functionName,
473
+ toolName: toolName,
474
+ query: args.query || args.timezone || 'N/A',
475
+ result: result
476
+ });
477
+ }
478
+
479
+ // Get AI's final response with tool results
480
+ const toolResultsText = toolResults.map(tr =>
481
+ `사용된 도구: ${tr.toolName}\n쿼리: ${tr.query}\n결과: ${tr.result}`
482
+ ).join('\n\n');
483
+
484
+ const finalContext = `${conversationContext}\n\n도구 실행 결과:\n${toolResultsText}\n\n위 도구 결과를 바탕으로 사용자에게 도움이 되는 답변을 해주세요. 답변 시작 부분에 어떤 도구를 사용했는지 간단히 언급해주세요.`;
485
+
486
+ const finalResponse = await puter.ai.chat(finalContext, {
487
+ model: 'gpt-5',
488
+ stream: true
489
+ });
490
+
491
+ // Stream the final response
492
+ for await (const part of finalResponse) {
493
+ if (part?.text) {
494
+ fullResponse += part.text;
495
+ updateStreamingMessage(aiMessageId, fullResponse, true);
496
+ }
497
+ }
498
+ } else {
499
+ // No tools needed, stream normally
500
+ const streamResponse = await puter.ai.chat(conversationContext, {
501
+ model: 'gpt-5-mini',
502
+ stream: true
503
+ });
504
+
505
+ for await (const part of streamResponse) {
506
+ if (part?.text) {
507
+ fullResponse += part.text;
508
+ updateStreamingMessage(aiMessageId, fullResponse, true);
509
+ }
510
+ }
511
+ }
512
+
513
+ // Remove cursor when streaming is complete
514
+ updateStreamingMessage(aiMessageId, fullResponse, false);
515
+
516
+ // Add AI response to conversation history
517
+ conversationHistory.push({
518
+ role: 'pixal',
519
+ message: fullResponse
520
+ });
521
+
522
+ // Update history status
523
+ updateHistoryStatus();
524
+
525
+ } catch (error) {
526
+ const errorMessage = '죄송합니다. 오류가 발생했습니다. 다시 시도해주세요.';
527
+ addMessage(errorMessage, 'ai', true);
528
+ console.error('Streaming chat error:', error);
529
+ } finally {
530
+ setLoading(false);
531
+ }
532
+ }
533
+
534
+ function buildConversationContext(currentMessage) {
535
+ let context = '';
536
+
537
+ // Add system message
538
+ context += '시스템 메시지: 너는 PIXAL(Primary Interactive X-ternal Assistant with multi Language)이야. 너의 개발자는 정성윤 이라는 6학년 파이썬 프로그래머야. 이 메시지는 사용자가 보낸것이 아닙니다.\n\n';
539
+
540
+ // Add previous conversation history
541
+ if (conversationHistory.length > 0) {
542
+ context += '이전 대화:\n';
543
+ conversationHistory.forEach(entry => {
544
+ if (entry.role === 'user') {
545
+ context += `사용자: ${entry.message}\n`;
546
+ } else {
547
+ context += `PIXAL: ${entry.message}\n`;
548
+ }
549
+ });
550
+ context += '\n현재 질문:\n';
551
+ }
552
+
553
+ context += `사용자: ${currentMessage}`;
554
+
555
+ return context;
556
+ }
557
+
558
+ function addMessage(content, sender, isError = false) {
559
+ const messageDiv = document.createElement('div');
560
+ messageDiv.className = `chat-message ${sender}-message message-bubble p-4 rounded-lg mb-4`;
561
+
562
+ if (sender === 'user') {
563
+ messageDiv.innerHTML = `
564
+ <div class="flex items-start space-x-3 justify-end">
565
+ <div class="flex-1 text-right">
566
+ <p class="text-white">${escapeHtml(content)}</p>
567
+ </div>
568
+ <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">
569
+ U
570
+ </div>
571
+ </div>
572
+ `;
573
+ } else {
574
+ messageDiv.innerHTML = `
575
+ <div class="flex items-start space-x-3">
576
+ <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">
577
+ AI
578
+ </div>
579
+ <div class="flex-1">
580
+ <div class="model-badge">PIXAL • GPT-5</div>
581
+ <div class="${isError ? 'text-red-600' : 'text-gray-800'} streaming-text">${formatAIResponse(content)}</div>
582
+ </div>
583
+ </div>
584
+ `;
585
+ }
586
+
587
+ messagesArea.appendChild(messageDiv);
588
+ scrollToBottom();
589
+ }
590
+
591
+ function createStreamingMessage() {
592
+ const messageDiv = document.createElement('div');
593
+ const messageId = 'streaming-' + Date.now();
594
+ messageDiv.id = messageId;
595
+ messageDiv.className = 'chat-message ai-message message-bubble p-4 rounded-lg mb-4';
596
+
597
+ messageDiv.innerHTML = `
598
+ <div class="flex items-start space-x-3">
599
+ <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">
600
+ AI
601
+ </div>
602
+ <div class="flex-1">
603
+ <div class="model-badge">PIXAL • Streaming</div>
604
+ <div class="text-gray-800 streaming-text" id="${messageId}-content">
605
+ <span class="streaming-cursor"></span>
606
+ </div>
607
+ </div>
608
+ </div>
609
+ `;
610
+
611
+ messagesArea.appendChild(messageDiv);
612
+ scrollToBottom();
613
+ return messageId;
614
+ }
615
+
616
+ function updateStreamingMessage(messageId, content, isStreaming) {
617
+ const contentElement = document.getElementById(messageId + '-content');
618
+ const badgeElement = document.querySelector(`#${messageId} .model-badge`);
619
+
620
+ if (contentElement) {
621
+ const formattedContent = formatStreamingResponse(content);
622
+ if (isStreaming) {
623
+ contentElement.innerHTML = formattedContent + '<span class="streaming-cursor"></span>';
624
+ badgeElement.textContent = 'PIXAL • Streaming';
625
+ } else {
626
+ contentElement.innerHTML = formattedContent;
627
+ badgeElement.textContent = 'PIXAL • GPT-5';
628
+ }
629
+ scrollToBottom();
630
+ }
631
+ }
632
+
633
+ function setLoading(loading) {
634
+ sendButton.disabled = loading;
635
+ messageInput.disabled = loading;
636
+
637
+ if (loading) {
638
+ sendButton.textContent = '응답 중...';
639
+ sendButton.classList.add('opacity-50', 'cursor-not-allowed');
640
+ } else {
641
+ sendButton.textContent = '전송';
642
+ sendButton.classList.remove('opacity-50', 'cursor-not-allowed');
643
+ }
644
+ }
645
+
646
+ function scrollToBottom() {
647
+ messagesArea.scrollTop = messagesArea.scrollHeight;
648
+ }
649
+
650
+ function escapeHtml(text) {
651
+ const div = document.createElement('div');
652
+ div.textContent = text;
653
+ return div.innerHTML;
654
+ }
655
+
656
+ function formatAIResponse(content) {
657
+ // Use marked.js to parse markdown
658
+ if (typeof marked !== 'undefined') {
659
+ return marked.parse(content);
660
+ }
661
+ // Fallback to simple formatting
662
+ return escapeHtml(content)
663
+ .replace(/\n\n/g, '</p><p class="mt-3">')
664
+ .replace(/\n/g, '<br>')
665
+ .replace(/^/, '<p>')
666
+ .replace(/$/, '</p>');
667
+ }
668
+
669
+ function formatStreamingResponse(content) {
670
+ // Use marked.js to parse markdown for streaming content
671
+ if (typeof marked !== 'undefined') {
672
+ return marked.parse(content);
673
+ }
674
+ // Fallback to simple formatting
675
+ return escapeHtml(content)
676
+ .replace(/\n\n/g, '</p><p class="mt-3">')
677
+ .replace(/\n/g, '<br>')
678
+ .replace(/^/, '<p>')
679
+ .replace(/$/, '</p>');
680
+ }
681
+
682
+ function updateHistoryStatus() {
683
+ const historyCount = Math.floor(conversationHistory.length / 2);
684
+ document.getElementById('history-status').textContent = `대화 기록: ${historyCount}개`;
685
+ }
686
+
687
+ function clearConversationHistory() {
688
+ conversationHistory = [];
689
+ updateHistoryStatus();
690
+
691
+ // Show confirmation message
692
+ const confirmDiv = document.createElement('div');
693
+ confirmDiv.className = 'chat-message ai-message message-bubble p-4 rounded-lg mb-4';
694
+ confirmDiv.innerHTML = `
695
+ <div class="flex items-start space-x-3">
696
+ <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">
697
+ AI
698
+ </div>
699
+ <div class="flex-1">
700
+ <div class="model-badge">System</div>
701
+ <p class="text-gray-600 italic">대화 기록이 초기화되었습니다. 새로운 대화를 시작해주세요!</p>
702
+ </div>
703
+ </div>
704
+ `;
705
+ messagesArea.appendChild(confirmDiv);
706
+ scrollToBottom();
707
+ }
708
+
709
+ // Event listeners
710
+ chatForm.addEventListener('submit', (e) => {
711
+ e.preventDefault();
712
+ const message = messageInput.value.trim();
713
+ if (message && !sendButton.disabled) {
714
+ sendMessage(message);
715
+ }
716
+ });
717
+
718
+ document.getElementById('clear-history').addEventListener('click', () => {
719
+ clearConversationHistory();
720
+ });
721
+
722
+ // Focus input on load
723
+ messageInput.focus();
724
+
725
+ // Element SDK implementation
726
+ async function onConfigChange(newConfig) {
727
+ config = { ...config, ...newConfig };
728
+
729
+ // Update UI elements
730
+ document.getElementById('chat-title').textContent = config.chat_title || defaultConfig.chat_title;
731
+ document.getElementById('welcome-message').textContent = config.welcome_message || defaultConfig.welcome_message;
732
+ }
733
+
734
+ function mapToCapabilities(config) {
735
+ return {
736
+ recolorables: [],
737
+ borderables: [],
738
+ fontEditable: undefined,
739
+ fontSizeable: undefined
740
+ };
741
+ }
742
+
743
+ function mapToEditPanelValues(config) {
744
+ return new Map([
745
+ ['chat_title', config.chat_title || defaultConfig.chat_title],
746
+ ['welcome_message', config.welcome_message || defaultConfig.welcome_message]
747
+ ]);
748
+ }
749
+
750
+ // Initialize Element SDK
751
+ if (window.elementSdk) {
752
+ window.elementSdk.init({
753
+ defaultConfig,
754
+ onConfigChange,
755
+ mapToCapabilities,
756
+ mapToEditPanelValues
757
+ });
758
+ }
759
+ </script>
760
+ <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>
761
+ </html>