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

Update index.html

Browse files
Files changed (1) hide show
  1. 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>รองรับ 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="แนบไฟล์">
@@ -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 (Reasoning)" },
428
- { value: "grok-3-mini-fast-beta", label: "Grok-3 Mini Fast Beta (Reasoning)" }
 
 
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
- const SYSTEM_PROMPT = "คุณคือผู้ช่วย AI ที่ช่วยเหลือด้านโค้ดและไอเดีย พร้อมอธิบายและยกตัวอย่างในบล็อก markdown (```) อย่างชัดเจน";
 
 
 
 
 
 
 
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 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) {
@@ -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>รองรับ Anthropic Claude-4-Sonnet และ xAI Grok-3 ล่าสุด!</div>';
603
  latestCodeContext = null;
604
  showContextSaveArea(false, null, goalInput.value);
605
  showError('');
@@ -613,25 +756,34 @@ providerSelect.addEventListener('change', function() {
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();
@@ -658,7 +810,7 @@ async function callAnthropicAPI(messages) {
658
  body: JSON.stringify({
659
  model: getModelValue(),
660
  max_tokens: 4096,
661
- system: SYSTEM_PROMPT,
662
  messages: messages
663
  })
664
  });
@@ -672,7 +824,61 @@ async function callAnthropicAPI(messages) {
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
 
@@ -685,7 +891,7 @@ async function callStreamingAPI(messages) {
685
  body: JSON.stringify({
686
  model: getModelValue(),
687
  messages: [
688
- { role: 'system', content: SYSTEM_PROMPT },
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
- if (!getApiKey()) {
 
 
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 = await callStreamingAPI(chatHistory);
 
 
 
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 (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>'));
@@ -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>