Spaces:
Running
Running
Update index.html
Browse files- index.html +273 -35
index.html
CHANGED
@@ -141,6 +141,30 @@
|
|
141 |
margin-left: 4px;
|
142 |
min-width: 80px;
|
143 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
144 |
.chat-container95 {
|
145 |
flex: 1 1 0%;
|
146 |
overflow-y: auto;
|
@@ -319,6 +343,12 @@
|
|
319 |
gap: 4px;
|
320 |
padding: 8px 4px;
|
321 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
322 |
.input-area95 {
|
323 |
flex-direction: column;
|
324 |
gap: 8px;
|
@@ -358,6 +388,8 @@
|
|
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>
|
@@ -366,6 +398,18 @@
|
|
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>
|
@@ -376,7 +420,7 @@
|
|
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
|
380 |
</div>
|
381 |
<form id="chatForm95" class="input-area95" autocomplete="off">
|
382 |
<button type="button" class="file-attach-btn" id="fileAttachBtn" title="แนบไฟล์">
|
@@ -412,7 +456,9 @@ const PROVIDERS = {
|
|
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"
|
@@ -424,8 +470,10 @@ const PROVIDERS = {
|
|
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
|
428 |
-
{ value: "grok-3-mini-fast-beta", label: "Grok-3 Mini Fast Beta
|
|
|
|
|
429 |
],
|
430 |
isStream: true
|
431 |
},
|
@@ -434,9 +482,11 @@ const PROVIDERS = {
|
|
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
|
@@ -447,14 +497,50 @@ const PROVIDERS = {
|
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
458 |
const PAYLOAD_CHAR_WARNING_THRESHOLD = 14000;
|
459 |
|
460 |
// --- Elements
|
@@ -463,6 +549,8 @@ 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');
|
@@ -525,13 +613,36 @@ function setModelOptions(providerKey) {
|
|
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) {
|
@@ -548,9 +659,41 @@ function saveApiKey(providerKey, key) {
|
|
548 |
apiKeyStatus.style.color = "#008000";
|
549 |
}
|
550 |
|
551 |
-
function
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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) {
|
@@ -599,7 +742,7 @@ function showContextSaveArea(show, code = null, goal = '') {
|
|
599 |
|
600 |
function clearChat() {
|
601 |
chatHistory = [];
|
602 |
-
messagesDiv.innerHTML = '<div class="message95 ai">สวัสดี! AI Chat สไตล์ Win95 Enhanced พร้อมใช้ 🎉<br
|
603 |
latestCodeContext = null;
|
604 |
showContextSaveArea(false, null, goalInput.value);
|
605 |
showError('');
|
@@ -613,25 +756,34 @@ providerSelect.addEventListener('change', function() {
|
|
613 |
showError('');
|
614 |
});
|
615 |
|
616 |
-
|
617 |
-
|
618 |
-
|
|
|
619 |
|
620 |
-
// ---
|
621 |
-
|
622 |
-
const
|
623 |
-
|
624 |
-
|
625 |
-
|
626 |
-
|
|
|
|
|
|
|
627 |
}
|
628 |
-
saveApiKey(getProviderKey(), key);
|
629 |
-
showError("");
|
630 |
});
|
631 |
|
632 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
633 |
|
634 |
-
// --- Anthropic API (Messages format
|
635 |
// --- API Key Save
|
636 |
confirmApiKeyBtn.addEventListener('click', function() {
|
637 |
const key = getApiKey();
|
@@ -658,7 +810,7 @@ async function callAnthropicAPI(messages) {
|
|
658 |
body: JSON.stringify({
|
659 |
model: getModelValue(),
|
660 |
max_tokens: 4096,
|
661 |
-
system:
|
662 |
messages: messages
|
663 |
})
|
664 |
});
|
@@ -672,7 +824,61 @@ async function callAnthropicAPI(messages) {
|
|
672 |
return data.content[0].text;
|
673 |
}
|
674 |
|
675 |
-
// ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
676 |
async function callStreamingAPI(messages) {
|
677 |
const provider = PROVIDERS[getProviderKey()];
|
678 |
|
@@ -685,7 +891,7 @@ async function callStreamingAPI(messages) {
|
|
685 |
body: JSON.stringify({
|
686 |
model: getModelValue(),
|
687 |
messages: [
|
688 |
-
{ role: 'system', content:
|
689 |
...messages
|
690 |
],
|
691 |
stream: true,
|
@@ -784,7 +990,9 @@ chatForm.addEventListener('submit', async function(e) {
|
|
784 |
|
785 |
const message = userInput.value.trim();
|
786 |
if (!message && attachedFiles.length === 0) return;
|
787 |
-
|
|
|
|
|
788 |
showError("กรุณาใส่ API Key ก่อนส่งข้อความ");
|
789 |
return;
|
790 |
}
|
@@ -839,7 +1047,6 @@ chatForm.addEventListener('submit', async function(e) {
|
|
839 |
}
|
840 |
|
841 |
try {
|
842 |
-
const providerKey = getProviderKey();
|
843 |
let aiResponse = '';
|
844 |
|
845 |
if (providerKey === 'anthropic') {
|
@@ -850,9 +1057,20 @@ chatForm.addEventListener('submit', async function(e) {
|
|
850 |
const code = extractCode(aiResponse);
|
851 |
if (code) latestCodeContext = code;
|
852 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
853 |
} else {
|
854 |
// Streaming response for other providers
|
855 |
-
const response =
|
|
|
|
|
|
|
856 |
const aiDiv = addMessage('ai', '');
|
857 |
const reader = response.body.getReader();
|
858 |
const decoder = new TextDecoder();
|
@@ -869,13 +1087,11 @@ chatForm.addEventListener('submit', async function(e) {
|
|
869 |
buffer = lines.pop() || '';
|
870 |
|
871 |
for (const line of lines) {
|
872 |
-
if (
|
873 |
-
|
874 |
-
if (data === '[DONE]') continue;
|
875 |
-
|
876 |
try {
|
877 |
-
const parsed = JSON.parse(
|
878 |
-
const delta = parsed.
|
879 |
if (delta) {
|
880 |
fullResponse += delta;
|
881 |
aiDiv.innerHTML = renderWithHighlight(fullResponse.replace(/\n/g, '<br>'));
|
@@ -885,6 +1101,25 @@ chatForm.addEventListener('submit', async function(e) {
|
|
885 |
} catch (e) {
|
886 |
// Skip malformed JSON
|
887 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
888 |
}
|
889 |
}
|
890 |
}
|
@@ -928,4 +1163,7 @@ document.addEventListener('keydown', function(e) {
|
|
928 |
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
|
929 |
chatForm.dispatchEvent(new Event('submit'));
|
930 |
}
|
931 |
-
});
|
|
|
|
|
|
|
|
141 |
margin-left: 4px;
|
142 |
min-width: 80px;
|
143 |
}
|
144 |
+
.system-preset-area {
|
145 |
+
background: #f0f0f0;
|
146 |
+
border-bottom: 2px solid #808080;
|
147 |
+
padding: 8px 12px;
|
148 |
+
display: flex;
|
149 |
+
flex-wrap: wrap;
|
150 |
+
align-items: center;
|
151 |
+
font-size: 0.95em;
|
152 |
+
gap: 8px;
|
153 |
+
}
|
154 |
+
.system-preset-area label {
|
155 |
+
margin-right: 4px;
|
156 |
+
font-weight: bold;
|
157 |
+
}
|
158 |
+
.system-preset-area select {
|
159 |
+
font-family: inherit;
|
160 |
+
font-size: 1em;
|
161 |
+
background: #fff;
|
162 |
+
border: 2px inset #808080;
|
163 |
+
padding: 2px 6px;
|
164 |
+
outline: none;
|
165 |
+
min-width: 120px;
|
166 |
+
max-width: 200px;
|
167 |
+
}
|
168 |
.chat-container95 {
|
169 |
flex: 1 1 0%;
|
170 |
overflow-y: auto;
|
|
|
343 |
gap: 4px;
|
344 |
padding: 8px 4px;
|
345 |
}
|
346 |
+
.system-preset-area {
|
347 |
+
flex-direction: column;
|
348 |
+
align-items: flex-start;
|
349 |
+
gap: 4px;
|
350 |
+
padding: 6px 4px;
|
351 |
+
}
|
352 |
.input-area95 {
|
353 |
flex-direction: column;
|
354 |
gap: 8px;
|
|
|
388 |
<option value="xai">xAI (Grok-3)</option>
|
389 |
<option value="groq">Groq</option>
|
390 |
<option value="openai">OpenAI</option>
|
391 |
+
<option value="ollama">Ollama (Local)</option>
|
392 |
+
<option value="huggingface">HuggingFace</option>
|
393 |
</select>
|
394 |
<label for="modelSelect95">โมเดล:</label>
|
395 |
<select id="modelSelect95"></select>
|
|
|
398 |
<button id="confirmApiKeyBtn95" type="button">บันทึก</button>
|
399 |
<span class="api-status" id="apiKeyStatus95"></span>
|
400 |
</form>
|
401 |
+
|
402 |
+
<div class="system-preset-area">
|
403 |
+
<label for="systemPresetSelect95">ระบบ:</label>
|
404 |
+
<select id="systemPresetSelect95">
|
405 |
+
<option value="general">แชทธรรมดา</option>
|
406 |
+
<option value="code-full">ส่งโค้ดเต็ม</option>
|
407 |
+
<option value="code-function">ส่งเฉพาะฟังก์ชัน</option>
|
408 |
+
<option value="custom">กำหนดเอง</option>
|
409 |
+
</select>
|
410 |
+
<input type="text" id="customSystemPrompt95" placeholder="ใส่ System Prompt ของคุณ" style="display:none; flex: 1; min-width: 200px;">
|
411 |
+
</div>
|
412 |
+
|
413 |
<div id="contextSaveArea95">
|
414 |
<h4>Context ปัจจุบัน</h4>
|
415 |
<div id="warningMessage95" style="color:orange;font-weight:bold;"></div>
|
|
|
420 |
<button id="confirmSaveBtn95">ยืนยัน context และเริ่มแชทใหม่</button>
|
421 |
</div>
|
422 |
<div class="chat-container95" id="messagesDiv95">
|
423 |
+
<div class="message95 ai">สวัสดี! AI Chat สไตล์ Win95 Enhanced พร้อมใช้ 🎉<br>รองรับหลาย AI Provider และมีระบบ preset ใหม่!</div>
|
424 |
</div>
|
425 |
<form id="chatForm95" class="input-area95" autocomplete="off">
|
426 |
<button type="button" class="file-attach-btn" id="fileAttachBtn" title="แนบไฟล์">
|
|
|
456 |
models: [
|
457 |
{ value: "claude-3-5-sonnet-20241022", label: "Claude-3.5 Sonnet" },
|
458 |
{ value: "claude-3-5-haiku-20241022", label: "Claude-3.5 Haiku" },
|
459 |
+
{ value: "claude-3-opus-20240229", label: "Claude-3 Opus" },
|
460 |
+
{ value: "claude-3-sonnet-20240229", label: "Claude-3 Sonnet" },
|
461 |
+
{ value: "claude-3-haiku-20240307", label: "Claude-3 Haiku" }
|
462 |
],
|
463 |
isStream: false,
|
464 |
streamEndpoint: "https://api.anthropic.com/v1/messages"
|
|
|
470 |
models: [
|
471 |
{ value: "grok-3-beta", label: "Grok-3 Beta" },
|
472 |
{ value: "grok-3-fast-beta", label: "Grok-3 Fast Beta" },
|
473 |
+
{ value: "grok-3-mini-beta", label: "Grok-3 Mini Beta" },
|
474 |
+
{ value: "grok-3-mini-fast-beta", label: "Grok-3 Mini Fast Beta" },
|
475 |
+
{ value: "grok-2-1212", label: "Grok-2" },
|
476 |
+
{ value: "grok-2-mini", label: "Grok-2 Mini" }
|
477 |
],
|
478 |
isStream: true
|
479 |
},
|
|
|
482 |
endpoint: "https://api.groq.com/openai/v1/chat/completions",
|
483 |
apiKeyLabel: "Groq API Key",
|
484 |
models: [
|
485 |
+
{ value: "llama-3.3-70b-versatile", label: "Llama-3.3 70B" },
|
486 |
{ value: "llama3-70b-8192", label: "Llama-3 70B 8K" },
|
487 |
{ value: "llama3-8b-8192", label: "Llama-3 8B 8K" },
|
488 |
{ value: "mixtral-8x7b-32768", label: "Mixtral 8x7B 32K" },
|
489 |
+
{ value: "gemma2-9b-it", label: "Gemma-2 9B IT" },
|
490 |
{ value: "gemma-7b-it", label: "Gemma 7B IT" }
|
491 |
],
|
492 |
isStream: true
|
|
|
497 |
apiKeyLabel: "OpenAI API Key",
|
498 |
models: [
|
499 |
{ value: "gpt-4o", label: "GPT-4o" },
|
500 |
+
{ value: "gpt-4o-mini", label: "GPT-4o Mini" },
|
501 |
{ value: "gpt-4-turbo", label: "GPT-4 Turbo" },
|
502 |
+
{ value: "gpt-3.5-turbo", label: "GPT-3.5 Turbo" },
|
503 |
+
{ value: "o1-preview", label: "o1-preview" },
|
504 |
+
{ value: "o1-mini", label: "o1-mini" }
|
505 |
],
|
506 |
isStream: true
|
507 |
+
},
|
508 |
+
ollama: {
|
509 |
+
name: "Ollama (Local)",
|
510 |
+
endpoint: "http://localhost:11434/api/chat",
|
511 |
+
apiKeyLabel: "ไม่ต้องใส่ API Key",
|
512 |
+
models: [
|
513 |
+
{ value: "llama3.2", label: "Llama 3.2" },
|
514 |
+
{ value: "llama3.1", label: "Llama 3.1" },
|
515 |
+
{ value: "codellama", label: "Code Llama" },
|
516 |
+
{ value: "mistral", label: "Mistral" },
|
517 |
+
{ value: "gemma2", label: "Gemma 2" }
|
518 |
+
],
|
519 |
+
isStream: true
|
520 |
+
},
|
521 |
+
huggingface: {
|
522 |
+
name: "HuggingFace",
|
523 |
+
endpoint: "https://api-inference.huggingface.co/models",
|
524 |
+
apiKeyLabel: "HuggingFace API Key",
|
525 |
+
models: [
|
526 |
+
{ value: "microsoft/DialoGPT-medium", label: "DialoGPT Medium" },
|
527 |
+
{ value: "microsoft/DialoGPT-large", label: "DialoGPT Large" },
|
528 |
+
{ value: "facebook/blenderbot-400M-distill", label: "BlenderBot 400M" },
|
529 |
+
{ value: "google/flan-t5-large", label: "Flan-T5 Large" },
|
530 |
+
{ value: "bigscience/bloom-560m", label: "BLOOM 560M" }
|
531 |
+
],
|
532 |
+
isStream: false
|
533 |
}
|
534 |
};
|
535 |
|
536 |
+
// --- System Presets ---
|
537 |
+
const SYSTEM_PRESETS = {
|
538 |
+
general: "คุณคือผู้ช่วย AI ที่ช่วยเหลือตอบคำถามและแชทธรรมดาได้ทุกเรื่อง",
|
539 |
+
"code-full": "คุณคือผู้ช่วยโปรแกรมเมอร์ เมื่อผู้ใช้ถามเกี่ยวกับโค้ด ให้ส่งโค้ดเต็มทั้งไฟล์กลับมาเสมอ ห้ามตัดย่อหรือใช้ ... ต้องแสดงโค้ดครบทุกบรรทัด",
|
540 |
+
"code-function": "คุณคือผู้ช่วยโปรแกรมเมอร์ เมื่อผู้ใช้ถามเกี่ยวกับโค้ด ให้ส่งเฉพาะฟังก์ชันหรือส่วนที่เกี่ยวข้องกับคำถาม ไม่ต้องส่งโค้ดทั้งไฟล์",
|
541 |
+
custom: "" // จะใช้ค่าจาก input
|
542 |
+
};
|
543 |
+
|
544 |
const PAYLOAD_CHAR_WARNING_THRESHOLD = 14000;
|
545 |
|
546 |
// --- Elements
|
|
|
549 |
const apiKeyInput = document.getElementById('apiKey95');
|
550 |
const confirmApiKeyBtn = document.getElementById('confirmApiKeyBtn95');
|
551 |
const apiKeyStatus = document.getElementById('apiKeyStatus95');
|
552 |
+
const systemPresetSelect = document.getElementById('systemPresetSelect95');
|
553 |
+
const customSystemPrompt = document.getElementById('customSystemPrompt95');
|
554 |
const chatForm = document.getElementById('chatForm95');
|
555 |
const userInput = document.getElementById('userInput95');
|
556 |
const messagesDiv = document.getElementById('messagesDiv95');
|
|
|
613 |
PROVIDERS[providerKey].models.forEach(m =>
|
614 |
modelSelect.innerHTML += `<option value="${m.value}">${m.label}</option>`
|
615 |
);
|
616 |
+
|
617 |
+
// บันทึกและโหลดโมเดลที่เลือกไว้
|
618 |
+
const savedModel = localStorage.getItem("ai95model_" + providerKey);
|
619 |
+
if (savedModel && PROVIDERS[providerKey].models.find(m => m.value === savedModel)) {
|
620 |
+
modelSelect.value = savedModel;
|
621 |
+
}
|
622 |
}
|
623 |
|
624 |
function setApiKeyLabel(providerKey) {
|
625 |
apiKeyInput.placeholder = "กรอก " + PROVIDERS[providerKey].apiKeyLabel;
|
626 |
+
|
627 |
+
// ซ่อน API Key input สำหรับ Ollama
|
628 |
+
if (providerKey === 'ollama') {
|
629 |
+
apiKeyInput.style.display = 'none';
|
630 |
+
confirmApiKeyBtn.style.display = 'none';
|
631 |
+
document.querySelector('label[for="apiKey95"]').style.display = 'none';
|
632 |
+
} else {
|
633 |
+
apiKeyInput.style.display = 'inline-block';
|
634 |
+
confirmApiKeyBtn.style.display = 'inline-block';
|
635 |
+
document.querySelector('label[for="apiKey95"]').style.display = 'inline-block';
|
636 |
+
}
|
637 |
}
|
638 |
|
639 |
function loadApiKey(providerKey) {
|
640 |
+
if (providerKey === 'ollama') {
|
641 |
+
apiKeyStatus.textContent = '✔️ Local Mode';
|
642 |
+
apiKeyStatus.style.color = '#008000';
|
643 |
+
return;
|
644 |
+
}
|
645 |
+
|
646 |
const k = localStorage.getItem("ai95key_" + providerKey) || "";
|
647 |
apiKeyInput.value = k;
|
648 |
if (k) {
|
|
|
659 |
apiKeyStatus.style.color = "#008000";
|
660 |
}
|
661 |
|
662 |
+
function saveModel(providerKey, model) {
|
663 |
+
localStorage.setItem("ai95model_" + providerKey, model);
|
664 |
+
}
|
665 |
+
|
666 |
+
function saveSystemPreset(preset) {
|
667 |
+
localStorage.setItem("ai95systemPreset", preset);
|
668 |
+
}
|
669 |
+
|
670 |
+
function loadSystemPreset() {
|
671 |
+
const saved = localStorage.getItem("ai95systemPreset");
|
672 |
+
if (saved) {
|
673 |
+
systemPresetSelect.value = saved;
|
674 |
+
if (saved === 'custom') {
|
675 |
+
customSystemPrompt.style.display = 'inline-block';
|
676 |
+
customSystemPrompt.value = localStorage.getItem("ai95customPrompt") || "";
|
677 |
+
}
|
678 |
+
}
|
679 |
+
}
|
680 |
+
|
681 |
+
function getApiKey() {
|
682 |
+
const providerKey = getProviderKey();
|
683 |
+
return providerKey === 'ollama' ? 'local' : apiKeyInput.value.trim();
|
684 |
+
}
|
685 |
+
|
686 |
function getProviderKey() { return providerSelect.value; }
|
687 |
function getModelValue() { return modelSelect.value; }
|
688 |
+
|
689 |
+
function getCurrentSystemPrompt() {
|
690 |
+
const preset = systemPresetSelect.value;
|
691 |
+
if (preset === 'custom') {
|
692 |
+
return customSystemPrompt.value.trim() || SYSTEM_PRESETS.general;
|
693 |
+
}
|
694 |
+
return SYSTEM_PRESETS[preset] || SYSTEM_PRESETS.general;
|
695 |
+
}
|
696 |
+
|
697 |
function showError(msg) { errorDiv.textContent = msg || ""; errorDiv.style.display = msg ? "block" : "none"; }
|
698 |
|
699 |
function addMessage(role, content) {
|
|
|
742 |
|
743 |
function clearChat() {
|
744 |
chatHistory = [];
|
745 |
+
messagesDiv.innerHTML = '<div class="message95 ai">สวัสดี! AI Chat สไตล์ Win95 Enhanced พร้อมใช้ 🎉<br>รองรับหลาย AI Provider และมีระบบ preset ใหม่!</div>';
|
746 |
latestCodeContext = null;
|
747 |
showContextSaveArea(false, null, goalInput.value);
|
748 |
showError('');
|
|
|
756 |
showError('');
|
757 |
});
|
758 |
|
759 |
+
// --- Model Change
|
760 |
+
modelSelect.addEventListener('change', function() {
|
761 |
+
saveModel(getProviderKey(), getModelValue());
|
762 |
+
});
|
763 |
|
764 |
+
// --- System Preset Change
|
765 |
+
systemPresetSelect.addEventListener('change', function() {
|
766 |
+
const preset = systemPresetSelect.value;
|
767 |
+
saveSystemPreset(preset);
|
768 |
+
|
769 |
+
if (preset === 'custom') {
|
770 |
+
customSystemPrompt.style.display = 'inline-block';
|
771 |
+
customSystemPrompt.focus();
|
772 |
+
} else {
|
773 |
+
customSystemPrompt.style.display = 'none';
|
774 |
}
|
|
|
|
|
775 |
});
|
776 |
|
777 |
+
customSystemPrompt.addEventListener('input', function() {
|
778 |
+
localStorage.setItem("ai95customPrompt", this.value);
|
779 |
+
});
|
780 |
+
|
781 |
+
// --- Initialize
|
782 |
+
setModelOptions(getProviderKey());
|
783 |
+
setApiKeyLabel(getProviderKey());
|
784 |
+
loadApiKey(getProviderKey());
|
785 |
+
loadSystemPreset();
|
786 |
|
|
|
787 |
// --- API Key Save
|
788 |
confirmApiKeyBtn.addEventListener('click', function() {
|
789 |
const key = getApiKey();
|
|
|
810 |
body: JSON.stringify({
|
811 |
model: getModelValue(),
|
812 |
max_tokens: 4096,
|
813 |
+
system: getCurrentSystemPrompt(),
|
814 |
messages: messages
|
815 |
})
|
816 |
});
|
|
|
824 |
return data.content[0].text;
|
825 |
}
|
826 |
|
827 |
+
// --- Ollama API
|
828 |
+
async function callOllamaAPI(messages) {
|
829 |
+
const response = await fetch(PROVIDERS.ollama.endpoint, {
|
830 |
+
method: 'POST',
|
831 |
+
headers: {
|
832 |
+
'Content-Type': 'application/json'
|
833 |
+
},
|
834 |
+
body: JSON.stringify({
|
835 |
+
model: getModelValue(),
|
836 |
+
messages: [
|
837 |
+
{ role: 'system', content: getCurrentSystemPrompt() },
|
838 |
+
...messages
|
839 |
+
],
|
840 |
+
stream: true
|
841 |
+
})
|
842 |
+
});
|
843 |
+
|
844 |
+
if (!response.ok) {
|
845 |
+
const error = await response.text();
|
846 |
+
throw new Error(`Ollama API Error: ${response.status} - ${error}`);
|
847 |
+
}
|
848 |
+
|
849 |
+
return response;
|
850 |
+
}
|
851 |
+
|
852 |
+
// --- HuggingFace API
|
853 |
+
async function callHuggingFaceAPI(messages) {
|
854 |
+
const modelName = getModelValue();
|
855 |
+
const lastMessage = messages[messages.length - 1];
|
856 |
+
|
857 |
+
const response = await fetch(`${PROVIDERS.huggingface.endpoint}/${modelName}`, {
|
858 |
+
method: 'POST',
|
859 |
+
headers: {
|
860 |
+
'Content-Type': 'application/json',
|
861 |
+
'Authorization': `Bearer ${getApiKey()}`
|
862 |
+
},
|
863 |
+
body: JSON.stringify({
|
864 |
+
inputs: lastMessage.content,
|
865 |
+
parameters: {
|
866 |
+
max_length: 2048,
|
867 |
+
temperature: 0.7
|
868 |
+
}
|
869 |
+
})
|
870 |
+
});
|
871 |
+
|
872 |
+
if (!response.ok) {
|
873 |
+
const error = await response.text();
|
874 |
+
throw new Error(`HuggingFace API Error: ${response.status} - ${error}`);
|
875 |
+
}
|
876 |
+
|
877 |
+
const data = await response.json();
|
878 |
+
return data[0]?.generated_text || "ไม่สามารถสร้างคำตอบได้";
|
879 |
+
}
|
880 |
+
|
881 |
+
// --- Streaming API (xAI/OpenAI/Groq)
|
882 |
async function callStreamingAPI(messages) {
|
883 |
const provider = PROVIDERS[getProviderKey()];
|
884 |
|
|
|
891 |
body: JSON.stringify({
|
892 |
model: getModelValue(),
|
893 |
messages: [
|
894 |
+
{ role: 'system', content: getCurrentSystemPrompt() },
|
895 |
...messages
|
896 |
],
|
897 |
stream: true,
|
|
|
990 |
|
991 |
const message = userInput.value.trim();
|
992 |
if (!message && attachedFiles.length === 0) return;
|
993 |
+
|
994 |
+
const providerKey = getProviderKey();
|
995 |
+
if (providerKey !== 'ollama' && !getApiKey()) {
|
996 |
showError("กรุณาใส่ API Key ก่อนส่งข้อความ");
|
997 |
return;
|
998 |
}
|
|
|
1047 |
}
|
1048 |
|
1049 |
try {
|
|
|
1050 |
let aiResponse = '';
|
1051 |
|
1052 |
if (providerKey === 'anthropic') {
|
|
|
1057 |
const code = extractCode(aiResponse);
|
1058 |
if (code) latestCodeContext = code;
|
1059 |
|
1060 |
+
} else if (providerKey === 'huggingface') {
|
1061 |
+
aiResponse = await callHuggingFaceAPI(chatHistory);
|
1062 |
+
const aiDiv = addMessage('ai', aiResponse);
|
1063 |
+
|
1064 |
+
// Extract code from AI response
|
1065 |
+
const code = extractCode(aiResponse);
|
1066 |
+
if (code) latestCodeContext = code;
|
1067 |
+
|
1068 |
} else {
|
1069 |
// Streaming response for other providers
|
1070 |
+
const response = providerKey === 'ollama' ?
|
1071 |
+
await callOllamaAPI(chatHistory) :
|
1072 |
+
await callStreamingAPI(chatHistory);
|
1073 |
+
|
1074 |
const aiDiv = addMessage('ai', '');
|
1075 |
const reader = response.body.getReader();
|
1076 |
const decoder = new TextDecoder();
|
|
|
1087 |
buffer = lines.pop() || '';
|
1088 |
|
1089 |
for (const line of lines) {
|
1090 |
+
if (providerKey === 'ollama') {
|
1091 |
+
// Ollama format
|
|
|
|
|
1092 |
try {
|
1093 |
+
const parsed = JSON.parse(line);
|
1094 |
+
const delta = parsed.message?.content || '';
|
1095 |
if (delta) {
|
1096 |
fullResponse += delta;
|
1097 |
aiDiv.innerHTML = renderWithHighlight(fullResponse.replace(/\n/g, '<br>'));
|
|
|
1101 |
} catch (e) {
|
1102 |
// Skip malformed JSON
|
1103 |
}
|
1104 |
+
} else {
|
1105 |
+
// OpenAI-compatible format
|
1106 |
+
if (line.startsWith('data: ')) {
|
1107 |
+
const data = line.slice(6).trim();
|
1108 |
+
if (data === '[DONE]') continue;
|
1109 |
+
|
1110 |
+
try {
|
1111 |
+
const parsed = JSON.parse(data);
|
1112 |
+
const delta = parsed.choices?.[0]?.delta?.content || '';
|
1113 |
+
if (delta) {
|
1114 |
+
fullResponse += delta;
|
1115 |
+
aiDiv.innerHTML = renderWithHighlight(fullResponse.replace(/\n/g, '<br>'));
|
1116 |
+
aiDiv.querySelectorAll('pre code').forEach(block => hljs.highlightElement(block));
|
1117 |
+
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
1118 |
+
}
|
1119 |
+
} catch (e) {
|
1120 |
+
// Skip malformed JSON
|
1121 |
+
}
|
1122 |
+
}
|
1123 |
}
|
1124 |
}
|
1125 |
}
|
|
|
1163 |
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
|
1164 |
chatForm.dispatchEvent(new Event('submit'));
|
1165 |
}
|
1166 |
+
});
|
1167 |
+
</script>
|
1168 |
+
</body>
|
1169 |
+
</html>
|