protae5544 commited on
Commit
bd381fe
·
verified ·
1 Parent(s): 7b051c0

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +929 -55
index.html CHANGED
@@ -1,57 +1,931 @@
1
  <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>Gradio-Lite: Serverless Gradio Running Entirely in Your Browser</title>
7
- <meta name="description" content="Gradio-Lite: Serverless Gradio Running Entirely in Your Browser">
8
-
9
- <script type="module" crossorigin src="https://cdn.jsdelivr.net/npm/@gradio/lite/dist/lite.js"></script>
10
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@gradio/lite/dist/lite.css" />
11
-
12
- <style>
13
- html, body {
14
- margin: 0;
15
- padding: 0;
16
- height: 100%;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  }
18
- </style>
19
- </head>
20
- <body>
21
- <gradio-lite>
22
- <gradio-file name="app.py" entrypoint>
23
- import gradio as gr
24
-
25
- from filters import as_gray
26
-
27
- def process(input_image):
28
- output_image = as_gray(input_image)
29
- return output_image
30
-
31
- demo = gr.Interface(
32
- process,
33
- "image",
34
- "image",
35
- examples=["lion.jpg", "logo.png"],
36
- )
37
-
38
- demo.launch()
39
- </gradio-file>
40
-
41
- <gradio-file name="filters.py">
42
- from skimage.color import rgb2gray
43
-
44
- def as_gray(image):
45
- return rgb2gray(image)
46
- </gradio-file>
47
-
48
- <gradio-file name="lion.jpg" url="https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/test_data/lion.jpg" />
49
- <gradio-file name="logo.png" url="https://raw.githubusercontent.com/gradio-app/gradio/main/guides/assets/logo.png" />
50
-
51
- <gradio-requirements>
52
- # Same syntax as requirements.txt
53
- scikit-image
54
- </gradio-requirements>
55
- </gradio-lite>
56
- </body>
57
- </html>
 
 
 
 
 
1
  <!DOCTYPE html>
2
+ <html lang="th">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1.0">
6
+ <title>AI Chat - Win95 Fullscreen + Stream + File Upload</title>
7
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=IBM+Plex+Mono:400,700&display=swap">
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/vs.min.css">
9
+ <style>
10
+ html, body { height: 100%; margin: 0; }
11
+ body {
12
+ font-family: 'IBM Plex Mono', 'Consolas', 'Courier New', monospace;
13
+ background: #008080;
14
+ height: 100vh;
15
+ margin: 0;
16
+ display: flex;
17
+ flex-direction: column;
18
+ align-items: center;
19
+ justify-content: center;
20
+ min-height: 100vh;
21
+ }
22
+ .win95window {
23
+ background: #c0c0c0;
24
+ border: 2px solid #fff;
25
+ border-bottom: 2px solid #808080;
26
+ border-right: 2px solid #808080;
27
+ box-shadow: 4px 8px 0px #0008, 0 0 0 8px #6664;
28
+ min-width: 320px;
29
+ max-width: 500px;
30
+ width: 100%;
31
+ min-height: 600px;
32
+ display: flex;
33
+ flex-direction: column;
34
+ position: relative;
35
+ transition: all 0.29s cubic-bezier(.4,.85,.59,1.02);
36
+ z-index: 1;
37
+ }
38
+ .win95window.fullscreen {
39
+ position: fixed !important;
40
+ left: 0; top: 0; right: 0; bottom: 0;
41
+ min-width: 100vw; min-height: 100vh; max-width: none; max-height: none;
42
+ width: 100vw; height: 100vh;
43
+ border-radius: 0;
44
+ box-shadow: none;
45
+ z-index: 9999;
46
+ }
47
+ .win95titlebar {
48
+ background: linear-gradient(90deg, #000080 80%, #1080c0 100%);
49
+ color: #fff;
50
+ padding: 7px 10px 7px 10px;
51
+ font-weight: bold;
52
+ font-size: 1.05em;
53
+ letter-spacing: .5px;
54
+ display: flex;
55
+ align-items: center;
56
+ justify-content: space-between;
57
+ border-bottom: 2px solid #808080;
58
+ user-select: none;
59
+ }
60
+ .win95titlebar .title {
61
+ display: flex;
62
+ align-items: center;
63
+ gap: 10px;
64
+ }
65
+ .win95titlebar .win95controls {
66
+ display: flex;
67
+ gap: 2px;
68
+ }
69
+ .win95titlebar button {
70
+ width: 18px; height: 18px; font-size: 1em;
71
+ border: 2px outset #fff;
72
+ background: #c0c0c0;
73
+ color: #000;
74
+ line-height: 1;
75
+ cursor: pointer;
76
+ outline: none;
77
+ margin-left: 2px;
78
+ padding: 0;
79
+ border-radius: 0;
80
+ }
81
+ .win95titlebar button:active {
82
+ border: 2px inset #808080;
83
+ background: #aaa;
84
+ }
85
+ .win95titlebar button:focus { outline: 1px dotted #fff; }
86
+ .win95content {
87
+ flex: 1 1 0%;
88
+ overflow: hidden;
89
+ display: flex;
90
+ flex-direction: column;
91
+ padding: 0;
92
+ min-height: 0;
93
+ }
94
+ .settings95 {
95
+ background: #e0e0e0;
96
+ border-bottom: 2px solid #808080;
97
+ padding: 10px 12px;
98
+ display: flex;
99
+ flex-wrap: wrap;
100
+ align-items: center;
101
+ font-size: 0.98em;
102
+ gap: 8px;
103
+ }
104
+ .settings95 label {
105
+ margin-right: 2px;
106
+ }
107
+ .settings95 select,
108
+ .settings95 input[type="text"] {
109
+ font-family: inherit;
110
+ font-size: 1em;
111
+ background: #fff;
112
+ border: 2px inset #808080;
113
+ padding: 2px 6px;
114
+ outline: none;
115
+ min-width: 96px;
116
+ max-width: 170px;
117
+ }
118
+ .settings95 input[type="text"]#apiKey95 {
119
+ min-width: 180px;
120
+ width: 150px;
121
+ max-width: 99vw;
122
+ }
123
+ .settings95 button {
124
+ font-family: inherit;
125
+ font-size: 1em;
126
+ background: #e0e0e0;
127
+ border: 2px outset #fff;
128
+ color: #000;
129
+ padding: 2.5px 14px;
130
+ margin-left: 2px;
131
+ cursor: pointer;
132
+ border-radius: 0;
133
+ }
134
+ .settings95 button:active {
135
+ border: 2px inset #808080;
136
+ background: #c0c0c0;
137
+ }
138
+ .settings95 .api-status {
139
+ font-size: 0.95em;
140
+ color: #008000;
141
+ margin-left: 4px;
142
+ min-width: 80px;
143
+ }
144
+ .chat-container95 {
145
+ flex: 1 1 0%;
146
+ overflow-y: auto;
147
+ background: #fff;
148
+ padding: 16px 8px;
149
+ border-top: 2px solid #fff;
150
+ border-bottom: 2px solid #808080;
151
+ display: flex;
152
+ flex-direction: column;
153
+ gap: 8px;
154
+ font-size: 1.01em;
155
+ min-height: 0;
156
+ }
157
+ .message95 {
158
+ max-width: 95%; word-break: break-word;
159
+ border: 2px solid #fff;
160
+ border-bottom: 2px solid #808080;
161
+ border-right: 2px solid #808080;
162
+ background: #e0e0e0;
163
+ margin-bottom: 0;
164
+ padding: 7px 10px;
165
+ border-radius: 0;
166
+ box-shadow: 2px 2px 0 #b0b0b0;
167
+ min-width: 70px;
168
+ position: relative;
169
+ }
170
+ .message95.ai { align-self: flex-start; background: #fffffe; }
171
+ .message95.user { align-self: flex-end; background: #c0e0ff; }
172
+ .message95 pre { background: #fff; border: 2px inset #808080; padding: 9px 5px 15px 5px; position: relative; white-space: pre-wrap; word-break: break-all; margin-bottom: 10px; margin-top: 7px; overflow-x: auto; }
173
+ .code-tools { position: absolute; right: 7px; top: 5px; z-index: 2; display: flex; gap: 3px; }
174
+ .code-tools button { font-size: 0.95em; padding: 1.5px 7px; background: #e0e0e0; border: 2px outset #fff; border-radius: 0; cursor: pointer; color: #222; }
175
+ .code-tools button:active { border: 2px inset #808080; background: #c0c0c0; }
176
+ .code-tools button:disabled { color: #aaa; border-color: #b0b0b0; }
177
+ .input-area95 {
178
+ background: #e0e0e0;
179
+ border-top: 2px solid #fff;
180
+ padding: 10px 10px 10px 10px;
181
+ display: flex;
182
+ gap: 10px;
183
+ align-items: stretch;
184
+ flex-wrap: wrap;
185
+ position: relative;
186
+ }
187
+ .input-area95 input[type="text"] {
188
+ font-family: inherit;
189
+ font-size: 1em;
190
+ border: 2px inset #808080;
191
+ background: #fff;
192
+ flex: 1 1 0%;
193
+ padding: 4px 8px;
194
+ min-width: 0;
195
+ }
196
+ .input-area95 button {
197
+ font-family: inherit;
198
+ font-size: 1em;
199
+ background: #e0e0e0;
200
+ border: 2px outset #fff;
201
+ color: #000;
202
+ padding: 3px 18px;
203
+ cursor: pointer;
204
+ border-radius: 0;
205
+ }
206
+ .input-area95 button:active {
207
+ border: 2px inset #808080;
208
+ background: #c0c0c0;
209
+ }
210
+ .input-area95 button:disabled {
211
+ background: #e0e0e0;
212
+ color: #aaa;
213
+ border: 2px outset #b0b0b0;
214
+ cursor: not-allowed;
215
+ }
216
+ .input-area95 .file-attach-btn {
217
+ position: relative;
218
+ overflow: hidden;
219
+ padding: 3px 8px;
220
+ font-size: 1em;
221
+ min-width: 1.5em;
222
+ border: 2px outset #fff;
223
+ background: #e0e0e0;
224
+ color: #333;
225
+ border-radius: 0;
226
+ cursor: pointer;
227
+ margin-right: 0;
228
+ margin-left: 0;
229
+ }
230
+ .input-area95 .file-attach-btn input[type="file"] {
231
+ position: absolute;
232
+ left: 0; top: 0; opacity: 0; width: 100%; height: 100%; cursor: pointer;
233
+ }
234
+ .input-area95 .file-attach-btn:active { border: 2px inset #808080; background: #c0c0c0; }
235
+ .input-area95 .file-name {
236
+ font-size: 0.98em; color: #333; margin-left: 5px; align-self: center; max-width: 110px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
237
+ }
238
+ .dropzone95 {
239
+ border: 2px dashed #008080;
240
+ background: #e6ffff;
241
+ color: #008080;
242
+ text-align: center;
243
+ font-size: 1.05em;
244
+ padding: 24px 5px 18px;
245
+ border-radius: 8px;
246
+ margin: 12px 5px;
247
+ transition: background 0.2s;
248
+ display: none;
249
+ z-index: 99;
250
+ position: absolute;
251
+ left: 0; right: 0; top: 0; bottom: 0;
252
+ pointer-events: all;
253
+ }
254
+ .error-message95 {
255
+ color: #b00;
256
+ background: #fff3f3;
257
+ border: 1px solid #d99;
258
+ padding: 7px 10px 7px 30px;
259
+ margin: 0;
260
+ font-size: 0.98em;
261
+ min-height: 20px;
262
+ background-image: url('data:image/svg+xml;utf8,<svg width="16" height="16" fill="red" xmlns="http://www.w3.org/2000/svg"><circle cx="8" cy="8" r="7" fill="white" stroke="red" stroke-width="1"/><text x="8" y="12" text-anchor="middle" font-size="12" fill="red" font-family="Arial" dy="-2">!</text></svg>');
263
+ background-repeat: no-repeat;
264
+ background-position: 6px 7px;
265
+ background-size: 16px 16px;
266
+ }
267
+ #contextSaveArea95 {
268
+ padding: 10px 10px 12px;
269
+ background: #f8f8e0;
270
+ border-top: 2px solid #fff;
271
+ border-bottom: 2px solid #808080;
272
+ display: none;
273
+ font-size: 0.98em;
274
+ }
275
+ #contextSaveArea95 h4 { margin: 0 0 8px 0; color: #444; font-size: 1.08em; }
276
+ #contextSaveArea95 pre {
277
+ background: #fff;
278
+ border: 2px inset #808080;
279
+ padding: 8px 5px;
280
+ font-size: 0.95em;
281
+ max-height: 120px;
282
+ overflow: auto;
283
+ margin: 7px 0;
284
+ white-space: pre-wrap;
285
+ word-break: break-all;
286
+ }
287
+ #contextSaveArea95 label { font-weight: bold; }
288
+ #contextSaveArea95 input[type="text"] {
289
+ width: 98%;
290
+ min-width: 0;
291
+ border: 2px inset #808080;
292
+ background: #fff;
293
+ font-size: 1em;
294
+ margin-bottom: 3px;
295
+ padding: 4px 8px;
296
+ }
297
+ #contextSaveArea95 button {
298
+ margin-top: 6px;
299
+ font-size: 1em;
300
+ background: #e0e0e0;
301
+ border: 2px outset #fff;
302
+ color: #000;
303
+ padding: 2.5px 14px;
304
+ cursor: pointer;
305
+ border-radius: 0;
306
+ }
307
+ #contextSaveArea95 button:active { background: #c0c0c0; border: 2px inset #808080; }
308
+ #contextSaveArea95 button:disabled { background: #e0e0e0; color: #bbb; border: 2px outset #b0b0b0; }
309
+ @media (max-width: 650px) {
310
+ .win95window {
311
+ min-width: 0;
312
+ max-width: 98vw;
313
+ box-shadow: 1px 2px 0px #0006, 0 0 0 2px #6664;
314
+ }
315
+ .win95content { padding: 0; }
316
+ .settings95 {
317
+ flex-direction: column;
318
+ align-items: flex-start;
319
+ gap: 4px;
320
+ padding: 8px 4px;
321
+ }
322
+ .input-area95 {
323
+ flex-direction: column;
324
+ gap: 8px;
325
+ padding: 8px 4px;
326
+ }
327
+ .chat-container95 {
328
+ padding: 10px 2px;
329
+ font-size: 0.98em;
330
+ }
331
+ .dropzone95 {
332
+ font-size: 1em;
333
+ padding: 20px 2px 14px;
334
+ }
335
+ }
336
+ </style>
337
+ </head>
338
+ <body>
339
+ <div class="win95window" id="win95window">
340
+ <div class="win95titlebar" id="titlebar">
341
+ <div class="title">
342
+ <span style="display:inline-block;width:18px;height:18px;background:#fff;border:1px solid #808080;margin-right:7px;box-shadow:inset 2px 2px #c0c0c0;">
343
+ <span style="display:inline-block;width:9px;height:9px;background:#008080;margin:4px 0 0 4px;vertical-align:middle;"></span>
344
+ </span>
345
+ AI Chat - Windows 95 Enhanced
346
+ </div>
347
+ <div class="win95controls">
348
+ <button id="maximizeBtn" title="เต็มจอ">&#9633;</button>
349
+ <button onclick="window.location.reload()" title="รีเฟรช">&#9632;</button>
350
+ <button onclick="window.close()" title="ปิด">&#10006;</button>
351
+ </div>
352
+ </div>
353
+ <div class="win95content">
354
+ <form class="settings95" id="settingsForm95" autocomplete="off" onsubmit="return false;">
355
+ <label for="providerSelect95">API:</label>
356
+ <select id="providerSelect95">
357
+ <option value="anthropic">Anthropic (Claude)</option>
358
+ <option value="xai">xAI (Grok-3)</option>
359
+ <option value="groq">Groq</option>
360
+ <option value="openai">OpenAI</option>
361
+ </select>
362
+ <label for="modelSelect95">โมเดล:</label>
363
+ <select id="modelSelect95"></select>
364
+ <label for="apiKey95">API Key:</label>
365
+ <input type="text" id="apiKey95" placeholder="กรอก API Key ของคุณ">
366
+ <button id="confirmApiKeyBtn95" type="button">บันทึก</button>
367
+ <span class="api-status" id="apiKeyStatus95"></span>
368
+ </form>
369
+ <div id="contextSaveArea95">
370
+ <h4>Context ปัจจุบัน</h4>
371
+ <div id="warningMessage95" style="color:orange;font-weight:bold;"></div>
372
+ <div>โค้ดล่าสุดที่ตรวจพบ:</div>
373
+ <pre id="savedCodeDisplay95">ยังไม่พบโค้ด หรือโค้ดยังไม่แสดง</pre>
374
+ <label for="goalInput95">สรุปเป้าหมาย/คำอธิบาย:</label>
375
+ <input type="text" id="goalInput95" placeholder="เช่น เพิ่มระบบล็อกอิน">
376
+ <button id="confirmSaveBtn95">ยืนยัน context และเริ่มแชทใหม่</button>
377
+ </div>
378
+ <div class="chat-container95" id="messagesDiv95">
379
+ <div class="message95 ai">สวัสดี! AI Chat สไตล์ Win95 Enhanced พร้อมใช้ 🎉<br>รองรับ Anthropic Claude-4-Sonnet และ xAI Grok-3 ล่าสุด!</div>
380
+ </div>
381
+ <form id="chatForm95" class="input-area95" autocomplete="off">
382
+ <button type="button" class="file-attach-btn" id="fileAttachBtn" title="แนบไฟล์">
383
+ 📎<input type="file" id="fileInput" multiple>
384
+ </button>
385
+ <span class="file-name" id="fileName"></span>
386
+ <input type="text" id="userInput95" placeholder="พิมพ์คำถามหรือข้อความ..." autocomplete="off">
387
+ <button type="submit">ส่ง</button>
388
+ <div class="dropzone95" id="dropzone95">วางไฟล์ที่นี่เพื่อแนบ</div>
389
+ </form>
390
+ <div id="errorMessage95" class="error-message95"></div>
391
+ </div>
392
+ </div>
393
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
394
+ <script>
395
+ // --- ฟีเจอร์ Fullscreen ---
396
+ const win95window = document.getElementById('win95window');
397
+ const maximizeBtn = document.getElementById('maximizeBtn');
398
+ let isFullscreen = false;
399
+ maximizeBtn.onclick = function() {
400
+ isFullscreen = !isFullscreen;
401
+ win95window.classList.toggle('fullscreen', isFullscreen);
402
+ maximizeBtn.innerHTML = isFullscreen ? "&#9632;" : "&#9633;";
403
+ maximizeBtn.title = isFullscreen ? "คืนหน้าต่าง" : "เต็มจอ";
404
+ };
405
+
406
+ // --- Provider/API Setup ---
407
+ const PROVIDERS = {
408
+ anthropic: {
409
+ name: "Anthropic (Claude)",
410
+ endpoint: "https://api.anthropic.com/v1/messages",
411
+ apiKeyLabel: "Anthropic API Key",
412
+ models: [
413
+ { value: "claude-3-5-sonnet-20241022", label: "Claude-3.5 Sonnet" },
414
+ { value: "claude-3-5-haiku-20241022", label: "Claude-3.5 Haiku" },
415
+ { value: "claude-3-opus-20240229", label: "Claude-3 Opus" }
416
+ ],
417
+ isStream: false,
418
+ streamEndpoint: "https://api.anthropic.com/v1/messages"
419
+ },
420
+ xai: {
421
+ name: "xAI (Grok)",
422
+ endpoint: "https://api.x.ai/v1/chat/completions",
423
+ apiKeyLabel: "xAI API Key",
424
+ models: [
425
+ { value: "grok-3-beta", label: "Grok-3 Beta" },
426
+ { value: "grok-3-fast-beta", label: "Grok-3 Fast Beta" },
427
+ { value: "grok-3-mini-beta", label: "Grok-3 Mini Beta (Reasoning)" },
428
+ { value: "grok-3-mini-fast-beta", label: "Grok-3 Mini Fast Beta (Reasoning)" }
429
+ ],
430
+ isStream: true
431
+ },
432
+ groq: {
433
+ name: "Groq",
434
+ endpoint: "https://api.groq.com/openai/v1/chat/completions",
435
+ apiKeyLabel: "Groq API Key",
436
+ models: [
437
+ { value: "llama3-70b-8192", label: "Llama-3 70B 8K" },
438
+ { value: "llama3-8b-8192", label: "Llama-3 8B 8K" },
439
+ { value: "mixtral-8x7b-32768", label: "Mixtral 8x7B 32K" },
440
+ { value: "gemma-7b-it", label: "Gemma 7B IT" }
441
+ ],
442
+ isStream: true
443
+ },
444
+ openai: {
445
+ name: "OpenAI",
446
+ endpoint: "https://api.openai.com/v1/chat/completions",
447
+ apiKeyLabel: "OpenAI API Key",
448
+ models: [
449
+ { value: "gpt-4o", label: "GPT-4o" },
450
+ { value: "gpt-4-turbo", label: "GPT-4 Turbo" },
451
+ { value: "gpt-3.5-turbo", label: "GPT-3.5 Turbo" }
452
+ ],
453
+ isStream: true
454
+ }
455
+ };
456
+
457
+ const SYSTEM_PROMPT = "คุณคือผู้ช่วย AI ที่ช่วยเหลือด้านโค้ดและไอเดีย พร้อมอธิบายและยกตัวอย่างในบล็อก markdown (```) อย่างชัดเจน";
458
+ const PAYLOAD_CHAR_WARNING_THRESHOLD = 14000;
459
+
460
+ // --- Elements
461
+ const providerSelect = document.getElementById('providerSelect95');
462
+ const modelSelect = document.getElementById('modelSelect95');
463
+ const apiKeyInput = document.getElementById('apiKey95');
464
+ const confirmApiKeyBtn = document.getElementById('confirmApiKeyBtn95');
465
+ const apiKeyStatus = document.getElementById('apiKeyStatus95');
466
+ const chatForm = document.getElementById('chatForm95');
467
+ const userInput = document.getElementById('userInput95');
468
+ const messagesDiv = document.getElementById('messagesDiv95');
469
+ const errorDiv = document.getElementById('errorMessage95');
470
+ const contextSaveArea = document.getElementById('contextSaveArea95');
471
+ const savedCodeDisplay = document.getElementById('savedCodeDisplay95');
472
+ const goalInput = document.getElementById('goalInput95');
473
+ const confirmSaveBtn = document.getElementById('confirmSaveBtn95');
474
+ const warningMessage = document.getElementById('warningMessage95');
475
+ const fileAttachBtn = document.getElementById('fileAttachBtn');
476
+ const fileInput = document.getElementById('fileInput');
477
+ const fileNameSpan = document.getElementById('fileName');
478
+ const dropzone = document.getElementById('dropzone95');
479
+
480
+ // --- State
481
+ let chatHistory = [];
482
+ let isAITyping = false;
483
+ let latestCodeContext = null;
484
+ let savedCodeContext = null;
485
+ let savedGoal = null;
486
+ let attachedFiles = [];
487
+
488
+ // --- highlight.js + code tools
489
+ function renderWithHighlight(text) {
490
+ return text.replace(/```(\w+)?\n([\s\S]*?)```/g, function(_, lang, code) {
491
+ const safeCode = code.replace(/</g,"&lt;").replace(/>/g,"&gt;");
492
+ const language = lang && hljs.getLanguage(lang) ? lang : 'plaintext';
493
+ const codeId = 'code_' + Math.random().toString(36).slice(2);
494
+ return `<pre><div class="code-tools">
495
+ <button type="button" onclick="copyCode('${codeId}')">📋 Copy</button>
496
+ <button type="button" onclick="downloadCode('${codeId}','code.${lang||'txt'}')">⬇️ Save</button>
497
+ </div><code id="${codeId}" class="hljs language-${language}">${safeCode}</code></pre>`;
498
+ });
499
+ }
500
+
501
+ window.copyCode = function(id) {
502
+ const code = document.getElementById(id);
503
+ if (code) {
504
+ navigator.clipboard.writeText(code.textContent).then(()=>{
505
+ code.parentElement.querySelector('button').textContent='✅';
506
+ setTimeout(()=>code.parentElement.querySelector('button').textContent='📋 Copy',1000);
507
+ });
508
+ }
509
+ };
510
+
511
+ window.downloadCode = function(id, filename) {
512
+ const code = document.getElementById(id);
513
+ if (code) {
514
+ const blob = new Blob([code.textContent], {type: "text/plain"});
515
+ const a = document.createElement('a');
516
+ a.href = URL.createObjectURL(blob);
517
+ a.download = filename;
518
+ a.click();
519
+ setTimeout(()=>URL.revokeObjectURL(a.href), 2000);
520
+ }
521
+ };
522
+
523
+ function setModelOptions(providerKey) {
524
+ modelSelect.innerHTML = "";
525
+ PROVIDERS[providerKey].models.forEach(m =>
526
+ modelSelect.innerHTML += `<option value="${m.value}">${m.label}</option>`
527
+ );
528
+ }
529
+
530
+ function setApiKeyLabel(providerKey) {
531
+ apiKeyInput.placeholder = "กรอก " + PROVIDERS[providerKey].apiKeyLabel;
532
+ }
533
+
534
+ function loadApiKey(providerKey) {
535
+ const k = localStorage.getItem("ai95key_" + providerKey) || "";
536
+ apiKeyInput.value = k;
537
+ if (k) {
538
+ apiKeyStatus.textContent = '✔️ โหลด API Key อัตโนมัติ';
539
+ apiKeyStatus.style.color = '#008000';
540
+ } else {
541
+ apiKeyStatus.textContent = '';
542
+ }
543
+ }
544
+
545
+ function saveApiKey(providerKey, key) {
546
+ localStorage.setItem("ai95key_" + providerKey, key);
547
+ apiKeyStatus.textContent = "✔️ API Key ถูกบันทึกแล้ว";
548
+ apiKeyStatus.style.color = "#008000";
549
+ }
550
+
551
+ function getApiKey() { return apiKeyInput.value.trim(); }
552
+ function getProviderKey() { return providerSelect.value; }
553
+ function getModelValue() { return modelSelect.value; }
554
+ function showError(msg) { errorDiv.textContent = msg || ""; errorDiv.style.display = msg ? "block" : "none"; }
555
+
556
+ function addMessage(role, content) {
557
+ const div = document.createElement('div');
558
+ div.className = 'message95 ' + role;
559
+ div.innerHTML = renderWithHighlight(content.replace(/\n/g, '<br>'));
560
+ messagesDiv.appendChild(div);
561
+ div.querySelectorAll('pre code').forEach((block) => hljs.highlightElement(block));
562
+ messagesDiv.scrollTop = messagesDiv.scrollHeight;
563
+ return div;
564
+ }
565
+
566
+ function showLoading() { chatForm.querySelector('button[type="submit"]').disabled = true; isAITyping = true; }
567
+ function hideLoading() { chatForm.querySelector('button[type="submit"]').disabled = false; isAITyping = false; }
568
+
569
+ function extractCode(text) {
570
+ const codeBlockRegex = /```(?:\w+)?\s*\n([\s\S]*?)```/g;
571
+ let lastCode = null, match;
572
+ while ((match = codeBlockRegex.exec(text)) !== null) lastCode = match[1].trim();
573
+ return lastCode;
574
+ }
575
+
576
+ function calculatePayloadCharCount(messages) {
577
+ let count = 0;
578
+ for (const msg of messages) {
579
+ if (msg.content && typeof msg.content === 'string') count += msg.content.length;
580
+ if (msg.content && Array.isArray(msg.content)) {
581
+ for (const part of msg.content) {
582
+ if (part.text) count += part.text.length;
583
+ }
584
+ }
585
+ }
586
+ return count;
587
+ }
588
+
589
+ function showContextSaveArea(show, code = null, goal = '') {
590
+ if (show) {
591
+ savedCodeDisplay.textContent = code !== null ? code : 'ยังไม่พบโค้ดในข้อความ AI ล่าสุด';
592
+ if (goal !== undefined) goalInput.value = goal;
593
+ contextSaveArea.style.display = 'block';
594
+ } else {
595
+ contextSaveArea.style.display = 'none';
596
+ warningMessage.textContent = '';
597
+ }
598
+ }
599
+
600
+ function clearChat() {
601
+ chatHistory = [];
602
+ messagesDiv.innerHTML = '<div class="message95 ai">สวัสดี! AI Chat สไตล์ Win95 Enhanced พร้อมใช้ 🎉<br>รองรับ Anthropic Claude-4-Sonnet และ xAI Grok-3 ล่าสุด!</div>';
603
+ latestCodeContext = null;
604
+ showContextSaveArea(false, null, goalInput.value);
605
+ showError('');
606
+ }
607
+
608
+ // --- Provider Change
609
+ providerSelect.addEventListener('change', function() {
610
+ setModelOptions(getProviderKey());
611
+ setApiKeyLabel(getProviderKey());
612
+ loadApiKey(getProviderKey());
613
+ showError('');
614
+ });
615
+
616
+ setModelOptions(getProviderKey());
617
+ setApiKeyLabel(getProviderKey());
618
+ loadApiKey(getProviderKey());
619
+
620
+ // --- API Key Save
621
+ confirmApiKeyBtn.addEventListener('click', function() {
622
+ const key = getApiKey();
623
+ if (!key) {
624
+ apiKeyStatus.textContent = '';
625
+ showError("กรุณาใส่ API Key ก่อนกดบันทึก");
626
+ return;
627
+ }
628
+ saveApiKey(getProviderKey(), key);
629
+ showError("");
630
+ });
631
+
632
+ apiKeyInput.addEventListener('input', ()=>apiKeyStatus.textContent='');
633
+
634
+ // --- Anthropic API (Messages format
635
+ // --- API Key Save
636
+ confirmApiKeyBtn.addEventListener('click', function() {
637
+ const key = getApiKey();
638
+ if (!key) {
639
+ apiKeyStatus.textContent = '';
640
+ showError("กรุณาใส่ API Key ก่อนกดบันทึก");
641
+ return;
642
+ }
643
+ saveApiKey(getProviderKey(), key);
644
+ showError("");
645
+ });
646
+
647
+ apiKeyInput.addEventListener('input', ()=>apiKeyStatus.textContent='');
648
+
649
+ // --- Anthropic API (Messages format)
650
+ async function callAnthropicAPI(messages) {
651
+ const response = await fetch(PROVIDERS.anthropic.endpoint, {
652
+ method: 'POST',
653
+ headers: {
654
+ 'Content-Type': 'application/json',
655
+ 'x-api-key': getApiKey(),
656
+ 'anthropic-version': '2023-06-01'
657
+ },
658
+ body: JSON.stringify({
659
+ model: getModelValue(),
660
+ max_tokens: 4096,
661
+ system: SYSTEM_PROMPT,
662
+ messages: messages
663
+ })
664
+ });
665
+
666
+ if (!response.ok) {
667
+ const error = await response.text();
668
+ throw new Error(`Anthropic API Error: ${response.status} - ${error}`);
669
+ }
670
+
671
+ const data = await response.json();
672
+ return data.content[0].text;
673
+ }
674
+
675
+ // --- xAI/OpenAI/Groq API (Chat format with streaming)
676
+ async function callStreamingAPI(messages) {
677
+ const provider = PROVIDERS[getProviderKey()];
678
+
679
+ const response = await fetch(provider.endpoint, {
680
+ method: 'POST',
681
+ headers: {
682
+ 'Content-Type': 'application/json',
683
+ 'Authorization': `Bearer ${getApiKey()}`
684
+ },
685
+ body: JSON.stringify({
686
+ model: getModelValue(),
687
+ messages: [
688
+ { role: 'system', content: SYSTEM_PROMPT },
689
+ ...messages
690
+ ],
691
+ stream: true,
692
+ max_tokens: 4096
693
+ })
694
+ });
695
+
696
+ if (!response.ok) {
697
+ const error = await response.text();
698
+ throw new Error(`${provider.name} API Error: ${response.status} - ${error}`);
699
+ }
700
+
701
+ return response;
702
+ }
703
+
704
+ // --- File upload handling
705
+ fileInput.addEventListener('change', handleFileSelect);
706
+
707
+ function handleFileSelect(event) {
708
+ const files = Array.from(event.target.files);
709
+ attachedFiles = [];
710
+
711
+ files.forEach(file => {
712
+ if (file.size > 5 * 1024 * 1024) { // 5MB limit
713
+ showError(`ไฟล์ ${file.name} มีขนาดใหญ่เกิน 5MB`);
714
+ return;
715
+ }
716
+
717
+ const reader = new FileReader();
718
+ reader.onload = (e) => {
719
+ attachedFiles.push({
720
+ name: file.name,
721
+ type: file.type,
722
+ content: e.target.result
723
+ });
724
+ };
725
+
726
+ if (file.type.startsWith('image/')) {
727
+ reader.readAsDataURL(file);
728
+ } else {
729
+ reader.readAsText(file);
730
+ }
731
+ });
732
+
733
+ if (files.length > 0) {
734
+ fileNameSpan.textContent = files.length === 1 ? files[0].name : `${files.length} ไฟล์`;
735
+ } else {
736
+ fileNameSpan.textContent = '';
737
+ }
738
+ }
739
+
740
+ // --- Drag and drop
741
+ document.addEventListener('dragover', (e) => {
742
+ e.preventDefault();
743
+ dropzone.style.display = 'block';
744
+ });
745
+
746
+ document.addEventListener('dragleave', (e) => {
747
+ if (!e.relatedTarget || !dropzone.contains(e.relatedTarget)) {
748
+ dropzone.style.display = 'none';
749
+ }
750
+ });
751
+
752
+ dropzone.addEventListener('drop', (e) => {
753
+ e.preventDefault();
754
+ dropzone.style.display = 'none';
755
+
756
+ const files = Array.from(e.dataTransfer.files);
757
+ const mockEvent = { target: { files: files } };
758
+ handleFileSelect(mockEvent);
759
+ });
760
+
761
+ // --- Context save functionality
762
+ confirmSaveBtn.addEventListener('click', function() {
763
+ const code = savedCodeDisplay.textContent;
764
+ const goal = goalInput.value.trim();
765
+
766
+ if (code === 'ยังไม่พบโค้ดในข้อความ AI ล่าสุด' || !code) {
767
+ showError("ไม่พบโค้ดที่จะบันทึก");
768
+ return;
769
+ }
770
+
771
+ savedCodeContext = code;
772
+ savedGoal = goal;
773
+
774
+ clearChat();
775
+
776
+ if (savedCodeContext && savedGoal) {
777
+ addMessage('ai', `Context ถูกบันทึกแล้ว!\n\n**เป้าหมาย:** ${savedGoal}\n\n**โค้ดล่าสุด:**\n\`\`\`\n${savedCodeContext}\n\`\`\`\n\nพร้อมรับคำถามหรือคำสั่งเพิ่มเติม!`);
778
+ }
779
+ });
780
+
781
+ // --- Main chat submission
782
+ chatForm.addEventListener('submit', async function(e) {
783
+ e.preventDefault();
784
+
785
+ const message = userInput.value.trim();
786
+ if (!message && attachedFiles.length === 0) return;
787
+ if (!getApiKey()) {
788
+ showError("กรุณาใส่ API Key ก่อนส่งข้อความ");
789
+ return;
790
+ }
791
+ if (isAITyping) return;
792
+
793
+ showError('');
794
+
795
+ let userMessage = message;
796
+
797
+ // Add file contents to message
798
+ if (attachedFiles.length > 0) {
799
+ userMessage += '\n\n--- ไฟล์ที่แนบ ---\n';
800
+ attachedFiles.forEach(file => {
801
+ userMessage += `\n**${file.name}:**\n`;
802
+ if (file.type.startsWith('image/')) {
803
+ userMessage += `[รูปภาพ: ${file.type}]\n`;
804
+ } else {
805
+ userMessage += `${file.content}\n`;
806
+ }
807
+ });
808
+
809
+ // Clear attachments
810
+ attachedFiles = [];
811
+ fileNameSpan.textContent = '';
812
+ fileInput.value = '';
813
+ }
814
+
815
+ // Add saved context if available
816
+ if (savedCodeContext && savedGoal) {
817
+ userMessage = `**Context ที่บันทึกไว้:**\nเป้าหมาย: ${savedGoal}\nโค้ดล่าสุด:\n\`\`\`\n${savedCodeContext}\n\`\`\`\n\n**คำถาม/คำสั่งใหม่:**\n${userMessage}`;
818
+ savedCodeContext = null;
819
+ savedGoal = null;
820
+ }
821
+
822
+ addMessage('user', message);
823
+ userInput.value = '';
824
+ showLoading();
825
+
826
+ const currentMessage = { role: 'user', content: userMessage };
827
+ chatHistory.push(currentMessage);
828
+
829
+ // Check payload size
830
+ const payloadSize = calculatePayloadCharCount(chatHistory);
831
+ if (payloadSize > PAYLOAD_CHAR_WARNING_THRESHOLD) {
832
+ const newCode = extractCode(chatHistory[chatHistory.length - 1]?.content || '');
833
+ if (newCode) latestCodeContext = newCode;
834
+
835
+ warningMessage.textContent = `⚠️ ข้อความยาวเกิน ${PAYLOAD_CHAR_WARNING_THRESHOLD} ตัวอักษร (ปัจจุบัน: ${payloadSize})`;
836
+ showContextSaveArea(true, latestCodeContext);
837
+ hideLoading();
838
+ return;
839
+ }
840
+
841
+ try {
842
+ const providerKey = getProviderKey();
843
+ let aiResponse = '';
844
+
845
+ if (providerKey === 'anthropic') {
846
+ aiResponse = await callAnthropicAPI(chatHistory);
847
+ const aiDiv = addMessage('ai', aiResponse);
848
+
849
+ // Extract code from AI response
850
+ const code = extractCode(aiResponse);
851
+ if (code) latestCodeContext = code;
852
+
853
+ } else {
854
+ // Streaming response for other providers
855
+ const response = await callStreamingAPI(chatHistory);
856
+ const aiDiv = addMessage('ai', '');
857
+ const reader = response.body.getReader();
858
+ const decoder = new TextDecoder();
859
+
860
+ let buffer = '';
861
+ let fullResponse = '';
862
+
863
+ while (true) {
864
+ const { value, done } = await reader.read();
865
+ if (done) break;
866
+
867
+ buffer += decoder.decode(value, { stream: true });
868
+ const lines = buffer.split('\n');
869
+ buffer = lines.pop() || '';
870
+
871
+ for (const line of lines) {
872
+ if (line.startsWith('data: ')) {
873
+ const data = line.slice(6).trim();
874
+ if (data === '[DONE]') continue;
875
+
876
+ try {
877
+ const parsed = JSON.parse(data);
878
+ const delta = parsed.choices?.[0]?.delta?.content || '';
879
+ if (delta) {
880
+ fullResponse += delta;
881
+ aiDiv.innerHTML = renderWithHighlight(fullResponse.replace(/\n/g, '<br>'));
882
+ aiDiv.querySelectorAll('pre code').forEach(block => hljs.highlightElement(block));
883
+ messagesDiv.scrollTop = messagesDiv.scrollHeight;
884
+ }
885
+ } catch (e) {
886
+ // Skip malformed JSON
887
  }
888
+ }
889
+ }
890
+ }
891
+
892
+ aiResponse = fullResponse;
893
+
894
+ // Extract code from AI response
895
+ const code = extractCode(aiResponse);
896
+ if (code) latestCodeContext = code;
897
+ }
898
+
899
+ chatHistory.push({ role: 'assistant', content: aiResponse });
900
+
901
+ } catch (error) {
902
+ console.error('API Error:', error);
903
+ showError(error.message);
904
+ chatHistory.pop(); // Remove failed user message
905
+ } finally {
906
+ hideLoading();
907
+ }
908
+ });
909
+
910
+ // --- Initialize
911
+ window.addEventListener('load', function() {
912
+ userInput.focus();
913
+
914
+ // Auto-resize input
915
+ userInput.addEventListener('input', function() {
916
+ this.style.height = 'auto';
917
+ this.style.height = Math.min(this.scrollHeight, 120) + 'px';
918
+ });
919
+ });
920
+
921
+ // --- Keyboard shortcuts
922
+ document.addEventListener('keydown', function(e) {
923
+ if (e.key === 'F11') {
924
+ e.preventDefault();
925
+ maximizeBtn.click();
926
+ }
927
+
928
+ if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
929
+ chatForm.dispatchEvent(new Event('submit'));
930
+ }
931
+ });