diff --git a/package.json b/package.json index d2335db..54e0d0c 100644 --- a/package.json +++ b/package.json @@ -15,4 +15,4 @@ "@tailwindcss/vite": "^4.1.11", "tailwindcss": "^4.1.11" } -} +} \ No newline at end of file diff --git a/src/main.js b/src/main.js index e0ac993..2c3d244 100644 --- a/src/main.js +++ b/src/main.js @@ -6,7 +6,8 @@ const VNDB_API_BASE_URL = "https://api.vndb.org/kana"; // -- 如需进行项目调试,请修改 AI_TRANSLATE 变量为自己的 API 接口与 ApiKey // -- 除此以外,ai.searchgal.homes 接口无法为其他任何非正当请求提供 LLM 服务 const AI_TRANSLATE_API_URL = "https://ai.searchgal.homes/v1/chat/completions"; -const AI_TRANSLATE_API_KEY = "sk-Md5kXePgq6HJjPa1Cf3265511bEe4e4c888232A0837e371e"; +const AI_TRANSLATE_API_KEY = + "sk-Md5kXePgq6HJjPa1Cf3265511bEe4e4c888232A0837e371e"; const AI_TRANSLATE_MODEL = "Qwen/Qwen2.5-32B-Instruct"; const ITEMS_PER_PAGE = 10; @@ -51,7 +52,6 @@ const scrollToTopBtn = document.getElementById("scrollToTopBtn"); const scrollToCommentsBtn = document.getElementById("scrollToCommentsBtn"); // Get the comments button const lockViewBtn = document.getElementById("lock-view-btn"); - scrollToTopBtn.addEventListener("click", () => { window.scrollTo({ top: 0, @@ -70,7 +70,6 @@ scrollToCommentsBtn.addEventListener("click", (e) => { } }); - /** * 页面加载后初始化 */ @@ -78,7 +77,7 @@ window.addEventListener("DOMContentLoaded", () => { // Store the original background image // The original background is no longer set on load. if (backgroundLayer) { - backgroundLayer.style.backgroundImage = 'none'; + backgroundLayer.style.backgroundImage = "none"; } // 从 URL 获取 API 参数并填充输入框 @@ -106,8 +105,7 @@ window.addEventListener("DOMContentLoaded", () => { siteNavigationDiv = document.createElement("div"); siteNavigationDiv.id = "siteNavigation"; // Initial classes are minimal, updateNavigationLayout will set full classes - siteNavigationDiv.className = - "z-20 flex flex-col items-center"; + siteNavigationDiv.className = "z-20 flex flex-col items-center"; document.body.appendChild(siteNavigationDiv); navLinksContainer = document.createElement("div"); @@ -177,13 +175,13 @@ window.addEventListener("DOMContentLoaded", () => { const lockViewBtn = document.getElementById("lock-view-btn"); if (lockViewBtn) { lockViewBtn.disabled = true; // Disable by default - lockViewBtn.addEventListener('click', handleLockViewToggle); + lockViewBtn.addEventListener("click", handleLockViewToggle); } // Add spacebar listener for view toggle - window.addEventListener('keydown', (e) => { + window.addEventListener("keydown", (e) => { // Check if spacebar is pressed and the active element is not an input field - if (e.code === 'Space' && document.activeElement.tagName !== 'INPUT') { + if (e.code === "Space" && document.activeElement.tagName !== "INPUT") { e.preventDefault(); // Prevent default spacebar action (e.g., scrolling) // Only trigger if the button is actually visible and enabled if (lockViewBtn && !lockViewBtn.disabled) { @@ -200,10 +198,10 @@ async function handleLockViewToggle() { isViewTransitioning = true; if (isViewLocked) { - document.body.classList.remove('ai-view-active'); + document.body.classList.remove("ai-view-active"); await showMainContent(); } else { - document.body.classList.add('ai-view-active'); + document.body.classList.add("ai-view-active"); await hideMainContent(); } isViewLocked = !isViewLocked; @@ -216,7 +214,7 @@ async function handleLockViewToggle() { } function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)); } /** @@ -224,9 +222,9 @@ function sleep(ms) { * @param {string} xmlString The raw XML string from the AI. */ function renderAiView(xmlString) { - const aiResponseBox = document.getElementById('ai-response-box'); + const aiResponseBox = document.getElementById("ai-response-box"); if (!aiResponseBox) return; - aiResponseBox.innerHTML = ''; // Always re-render from scratch + aiResponseBox.innerHTML = ""; // Always re-render from scratch // Helper to get all content within a major block, even if incomplete const getBlockContent = (tagName, xml) => { @@ -244,65 +242,74 @@ function renderAiView(xmlString) { // Helper to extract all complete values from a block const getCompleteValues = (tagName, block) => { - const regex = new RegExp(`<${tagName}>([\\s\\S]*?)${tagName}>`, 'g'); - return [...block.matchAll(regex)].map(m => m[1]); + const regex = new RegExp(`<${tagName}>([\\s\\S]*?)${tagName}>`, "g"); + return [...block.matchAll(regex)].map((m) => m[1]); }; // 1. Game Description - const descriptionContent = getBlockContent('game_description_translated', xmlString); + const descriptionContent = getBlockContent( + "game_description_translated", + xmlString + ); if (descriptionContent) { // Render all complete paragraphs - getCompleteValues('p', descriptionContent).forEach(pText => { - const pElement = document.createElement('p'); + getCompleteValues("p", descriptionContent).forEach((pText) => { + const pElement = document.createElement("p"); pElement.innerHTML = ` ${pText}`; aiResponseBox.appendChild(pElement); }); // Check for and render an incomplete paragraph at the end - const lastPOpen = descriptionContent.lastIndexOf('
'); - const lastPClosed = descriptionContent.lastIndexOf('
'); + const lastPOpen = descriptionContent.lastIndexOf(""); + const lastPClosed = descriptionContent.lastIndexOf("
"); if (lastPOpen > lastPClosed) { - const pElement = document.createElement('p'); - pElement.innerHTML = ` ${descriptionContent.substring(lastPOpen + 3)}`; + const pElement = document.createElement("p"); + pElement.innerHTML = ` ${descriptionContent.substring( + lastPOpen + 3 + )}`; aiResponseBox.appendChild(pElement); } } // 2. Characters - const charactersContent = getBlockContent('characters_translated', xmlString); + const charactersContent = getBlockContent("characters_translated", xmlString); if (charactersContent) { - const charactersWrapper = document.createElement('div'); - charactersWrapper.className = 'mt-12'; + const charactersWrapper = document.createElement("div"); + charactersWrapper.className = "mt-12"; aiResponseBox.appendChild(charactersWrapper); const roleMap = { - main: { title: '主人公', order: 1 }, - primary: { title: '主要角色', order: 2 }, - side: { title: '次要角色', order: 3 }, - appears: { title: '配角', order: 4 } + main: { title: "主人公", order: 1 }, + primary: { title: "主要角色", order: 2 }, + side: { title: "次要角色", order: 3 }, + appears: { title: "配角", order: 4 }, }; - const characterBlocks = charactersContent.split('${description.replace(/\n/g, "
")}
${description.replace(
+ /\n/g,
+ "
"
+ )}
/g, '
'); + const endIndexDesc = vndbInfo.aiRawResponse.indexOf( + "
/g, + "
"
+ );
vndbDescription.innerHTML = contentToRenderDesc;
}
}
@@ -2062,7 +2150,7 @@ async function translateAndStreamDescription(description, characters) {
}
}
}
- boundary = buffer.indexOf('\n');
+ boundary = buffer.indexOf("\n");
}
}
} else {
@@ -2075,21 +2163,29 @@ async function translateAndStreamDescription(description, characters) {
// Construct fallback XML for AI view
let fallbackXml = " ${description.replace(/\n/g, " ")} ${description.replace(
+ /\n/g,
+ " "
+ )}