openfree commited on
Commit
cac1ea0
·
verified ·
1 Parent(s): e5058ab

add static/script.js

Browse files
Files changed (1) hide show
  1. static/script.js +25 -2
static/script.js CHANGED
@@ -7,6 +7,7 @@ const portalEl = $("#portal");
7
  const inputEl = $("#input");
8
  const sendBtn = $("#send-btn");
9
  const attachBtn = $("#attach-btn");
 
10
  const fileInput = $("#file-input");
11
  const attachRow = $("#attach-row");
12
  const chatListEl = $("#chat-list");
@@ -36,12 +37,24 @@ let chats = JSON.parse(localStorage.getItem("jgos_chats") || "[]");
36
  let activeChatId = null;
37
  let isStreaming = false;
38
  let pendingAttachments = [];
 
39
 
40
  const LEADER_KEYWORDS = ["시장", "구청장", "군수", "도지사", "단체장", "당선", "임기"];
41
  const ALLOWED_EXTS = ["hwp", "hwpx", "pdf", "docx", "pptx", "xlsx", "xls", "csv", "txt", "md",
42
  "jpg", "jpeg", "png", "gif", "webp"];
43
 
44
  function uuid() { return Math.random().toString(36).slice(2, 10) + Date.now().toString(36); }
 
 
 
 
 
 
 
 
 
 
 
45
  function saveChats() {
46
  // vision base64 image_url은 localStorage에 저장하지 않음 (quota 초과 방지) — 메모리엔 유지(전송용)
47
  const slim = () => chats.map((c) => ({
@@ -257,6 +270,14 @@ function isLeaderQuery(text) {
257
 
258
  // ===== 파일 업로드 =====
259
  attachBtn.onclick = () => fileInput.click();
 
 
 
 
 
 
 
 
260
  fileInput.onchange = (e) => { handleFiles(e.target.files); fileInput.value = ""; };
261
 
262
  // drag-drop
@@ -441,6 +462,7 @@ async function send(presetText) {
441
  let acc = "";
442
  let firstTokenReceived = false;
443
  let aborted = false;
 
444
 
445
  // ⭐ assistant 메시지를 미리 push (부분 저장 — 새로고침 시 답변 보존)
446
  const asstMsgIdx = chat.messages.push({ role: "assistant", content: "", _streaming: true }) - 1;
@@ -460,7 +482,7 @@ async function send(presetText) {
460
  signal: currentAbort.signal,
461
  body: JSON.stringify({
462
  messages: chat.messages.filter((m) => m.role !== "system" && !m._streaming),
463
- temperature: 0.3, max_tokens: 2048,
464
  }),
465
  });
466
  if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
@@ -484,6 +506,7 @@ async function send(presetText) {
484
  statusEl.classList.add("error"); statusEl.textContent = "● 오류";
485
  throw new Error(j.error);
486
  }
 
487
  const delta = j.choices?.[0]?.delta?.content || "";
488
  if (delta) {
489
  if (!firstTokenReceived) { firstTokenReceived = true; asstDiv.innerHTML = ""; }
@@ -495,7 +518,7 @@ async function send(presetText) {
495
  } catch (e) { /* skip non-JSON */ }
496
  }
497
  }
498
- asstDiv.innerHTML = renderMarkdown(acc);
499
  if (window.hljs) {
500
  asstDiv.querySelectorAll("pre code").forEach((b) => { try { hljs.highlightElement(b); } catch (e) {} });
501
  }
 
7
  const inputEl = $("#input");
8
  const sendBtn = $("#send-btn");
9
  const attachBtn = $("#attach-btn");
10
+ const webBtn = $("#web-btn");
11
  const fileInput = $("#file-input");
12
  const attachRow = $("#attach-row");
13
  const chatListEl = $("#chat-list");
 
37
  let activeChatId = null;
38
  let isStreaming = false;
39
  let pendingAttachments = [];
40
+ let webSearchOn = false; // 🔍 웹검색 토글 (네이버+웹 실시간 grounding)
41
 
42
  const LEADER_KEYWORDS = ["시장", "구청장", "군수", "도지사", "단체장", "당선", "임기"];
43
  const ALLOWED_EXTS = ["hwp", "hwpx", "pdf", "docx", "pptx", "xlsx", "xls", "csv", "txt", "md",
44
  "jpg", "jpeg", "png", "gif", "webp"];
45
 
46
  function uuid() { return Math.random().toString(36).slice(2, 10) + Date.now().toString(36); }
47
+ function escHtml(s) { return String(s == null ? "" : s).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[c])); }
48
+ function renderSources(srcs) {
49
+ if (!srcs || !srcs.length) return "";
50
+ const items = srcs.slice(0, 8).map((s, i) =>
51
+ `<a class="src-card" href="${escHtml(s.link)}" target="_blank" rel="noopener noreferrer">` +
52
+ `<span class="src-num">${i + 1}</span>` +
53
+ `<span class="src-body"><span class="src-title">${escHtml(s.title)}</span>` +
54
+ `<span class="src-tag">${escHtml(s.src)}</span></span></a>`
55
+ ).join("");
56
+ return `<div class="src-wrap"><div class="src-head">🔍 참고한 웹 출처</div><div class="src-list">${items}</div></div>`;
57
+ }
58
  function saveChats() {
59
  // vision base64 image_url은 localStorage에 저장하지 않음 (quota 초과 방지) — 메모리엔 유지(전송용)
60
  const slim = () => chats.map((c) => ({
 
270
 
271
  // ===== 파일 업로드 =====
272
  attachBtn.onclick = () => fileInput.click();
273
+ if (webBtn) webBtn.onclick = () => {
274
+ webSearchOn = !webSearchOn;
275
+ webBtn.classList.toggle("on", webSearchOn);
276
+ webBtn.setAttribute("aria-pressed", webSearchOn ? "true" : "false");
277
+ webBtn.title = webSearchOn
278
+ ? "웹검색 켜짐 — 최신 정보를 함께 검색합니다 (끄려면 클릭)"
279
+ : "웹검색 — 켜면 네이버·웹에서 최신 정보를 함께 검색합니다";
280
+ };
281
  fileInput.onchange = (e) => { handleFiles(e.target.files); fileInput.value = ""; };
282
 
283
  // drag-drop
 
462
  let acc = "";
463
  let firstTokenReceived = false;
464
  let aborted = false;
465
+ let lastSources = null;
466
 
467
  // ⭐ assistant 메시지를 미리 push (부분 저장 — 새로고침 시 답변 보존)
468
  const asstMsgIdx = chat.messages.push({ role: "assistant", content: "", _streaming: true }) - 1;
 
482
  signal: currentAbort.signal,
483
  body: JSON.stringify({
484
  messages: chat.messages.filter((m) => m.role !== "system" && !m._streaming),
485
+ temperature: 0.3, max_tokens: 2048, web: webSearchOn,
486
  }),
487
  });
488
  if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
 
506
  statusEl.classList.add("error"); statusEl.textContent = "● 오류";
507
  throw new Error(j.error);
508
  }
509
+ if (j.sources && Array.isArray(j.sources)) { lastSources = j.sources; }
510
  const delta = j.choices?.[0]?.delta?.content || "";
511
  if (delta) {
512
  if (!firstTokenReceived) { firstTokenReceived = true; asstDiv.innerHTML = ""; }
 
518
  } catch (e) { /* skip non-JSON */ }
519
  }
520
  }
521
+ asstDiv.innerHTML = renderMarkdown(acc) + renderSources(lastSources);
522
  if (window.hljs) {
523
  asstDiv.querySelectorAll("pre code").forEach((b) => { try { hljs.highlightElement(b); } catch (e) {} });
524
  }