Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
add static/script.js
Browse files- 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) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[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 |
}
|