1 line
12 KiB
JSON
1 line
12 KiB
JSON
{"repo": "asadahimeka/pixiv-viewer-app", "files": {"src/utils/translate.js": {"8d6cf02115f45294158d6b79f7211f63bd54cd9e": {"t": 1785674139.3124518, "n": 11248, "h": "b817b13d6aceb99d", "c": "import { Dialog } from 'vant'\nimport { i18n } from '@/i18n'\nimport { SILICON_CLOUD_API_KEY } from '@/consts'\nimport { loadScript } from '.'\n\nexport async function loadKISSTranslator(isAutoLoad = false) {\n if (!isAutoLoad && !localStorage.getItem('PXV_KISST_CFMED')) {\n const res = await Dialog.confirm({\n title: '加载 KISS Translator 脚本',\n message: '提示:如果已安装 KISS Translator 浏览器扩展或用户脚本则无需加载。可在偏好设置中打开自动加载 KISS Translator 脚本。',\n lockScroll: false,\n closeOnPopstate: true,\n cancelButtonText: '取消',\n confirmButtonText: '加载',\n }).catch(() => 'cancel')\n if (res != 'confirm') return\n localStorage.setItem('PXV_KISST_CFMED', '1')\n }\n if (document.querySelector('#kiss-translator')) {\n return\n }\n await loadScript('/kiss-translator/KISS-Translator.min.js')\n if (isAutoLoad) {\n setTimeout(() => {\n window._GM_menuCommands?.find(e => e.name == '开启翻译')?.callback()\n }, 1000)\n }\n}\n\n// export async function loadImtSdk(isAutoLoadImt = false) {\n// if (!isAutoLoadImt && !localStorage.getItem('PXV_IMT_SDK_CFMED')) {\n// const res = await Dialog.confirm({\n// title: '加载沉浸式翻译 SDK',\n// message: '提示:如果已安装沉浸式翻译浏览器扩展则无需加载沉浸式翻译 SDK。可在偏好设置中打开自动加载沉浸式翻译 SDK。',\n// lockScroll: false,\n// closeOnPopstate: true,\n// cancelButtonText: '取消',\n// confirmButtonText: '加载',\n// }).catch(() => 'cancel')\n// if (res != 'confirm') return\n// localStorage.setItem('PXV_IMT_SDK_CFMED', '1')\n// }\n// if (window.immersiveTranslateConfig) return\n// window.immersiveTranslateConfig = {\n// isAutoTranslate: isAutoLoadImt,\n// pageRule: {\n// selectors: [\n// '.novel_text',\n// '.artwork-meta .name-box .title',\n// '.artwork-meta .name-box .series',\n// '.artwork-meta .caption',\n// '.artwork .series-btns',\n// '.users .info .detail .content',\n// '.comments-popup .content',\n// '.live_detail .content .title',\n// '.live_detail .content .chat-item span:last-child',\n// ],\n// translationClasses: ['imt-res-text'],\n// },\n// }\n// await loadScript('https://download.immersivetranslate.com/immersive-translate-sdk-latest.js')\n// const style = document.createElement('style')\n// style.innerHTML = `\n// .imt-fb-more-buttons .btn-animate:first-child,\n// .imt-fb-more-buttons .btn-animate:last-child,\n// .btn-animate[title=\"关闭悬浮球\"],\n// .popup-container .popup-content > div.flex:first-child,\n// .popup-container .trial-pro-container,\n// .popup-container .text-sm.px-1.text-gray-2,\n// .popup-container .widgets-container.mt-5,\n// .popup-container footer,\n// .translation-service-container .custom-select-item:has(.custom-select-item-pro),\n// .translation-service-container select option[value=\"deepl\"],\n// .translation-service-container select option[value=\"openai\"],\n// .translation-service-container select option[value=\"gemini\"],\n// .translation-service-container select option[value=\"claude\"],\n// .translation-service-container select option[value=\"more\"] {\n// display: none !important;\n// }`\n// setTimeout(() => {\n// document.querySelector('#immersive-translate-popup')?.shadowRoot?.appendChild(style)\n// try {\n// const buildinConfig = JSON.parse(localStorage.buildinConfig).buildinConfig\n// buildinConfig.telemetry = false\n// // buildinConfig.translationService = 'bing'\n// buildinConfig.translationService = 'siliconcloud'\n// buildinConfig.translationServices.siliconcloud.APIKEY = SILICON_CLOUD_API_KEY\n// buildinConfig.translationServices.siliconcloud.assistantId = 'common'\n// buildinConfig.translationServices.siliconcloud.model = 'Qwen/Qwen2-7B-Instruct'\n// localStorage.buildinConfig = JSON.stringify({ buildinConfig })\n// const userConfig = JSON.parse(localStorage.userConfig).userConfig\n// userConfig.telemetry = false\n// // userConfig.translationService = 'bing'\n// userConfig.translationService = 'siliconcloud'\n// userConfig.translationServices = userConfig.translationServices || {}\n// userConfig.translationServices.siliconcloud = userConfig.translationServices.siliconcloud || {}\n// userConfig.translationServices.siliconcloud.APIKEY = SILICON_CLOUD_API_KEY\n// userConfig.translationServices.siliconcloud.assistantId = 'common'\n// userConfig.translationServices.siliconcloud.model = 'Qwen/Qwen2-7B-Instruct'\n// localStorage.userConfig = JSON.stringify({ userConfig })\n// } catch (err) {\n// console.log('err: ', err)\n// }\n// }, 800)\n// }\n\nexport function getNoTranslateWords(tags = []) {\n return new Promise(resolve => {\n Dialog.confirm({\n title: '填写不翻译的文本',\n message: `\n <div id=\"get_pnt_nots_dialog\">\n <p style=\"margin:0.2rem 0\">选择或输入不翻译的单词,以英文逗号分隔,留空跳过</p>\n <input id=\"get_pnt_nots_input\" type=\"text\" >\n <div style=\"height:1px;margin:0.2rem 0;border-bottom:1px solid #ccc\"></div>\n ${tags.map(e => `<div class=\"sel_block_chks\"><input type=\"checkbox\" data-tagname=\"${e.name}\" onchange=\"if(this.checked){window['get_pnt_nots_input'].value=window['get_pnt_nots_input'].value.split(',').filter(e=>e).concat([this.getAttribute('data-tagname')]).join(',')}else{window['get_pnt_nots_input'].value=window['get_pnt_nots_input'].value.split(',').filter(e=>e&&e!=this.getAttribute('data-tagname')).join(',')}\">${e.name}</div>`).join('')}\n </div>`,\n lockScroll: false,\n closeOnPopstate: true,\n cancelButtonText: i18n.t('common.cancel'),\n confirmButtonText: i18n.t('common.confirm'),\n beforeClose: (action, done) => {\n if (action == 'confirm') {\n const tagInp = document.querySelector('#get_pnt_nots_input')?.value || ''\n resolve(tagInp)\n }\n done()\n },\n }).catch(() => {})\n })\n}\n\nexport const aiModelMap = {\n glm: 'THUDM/glm-4-9b-chat',\n qwen2_5: 'Qwen/Qwen2.5-7B-Instruct',\n hy_mt: 'tencent/Hunyuan-MT-7B',\n qwen2: 'Qwen/Qwen2-7B-Instruct',\n qwen3: 'Qwen/Qwen3-8B',\n // glm_0414: 'THUDM/GLM-4-9B-0414',\n // glm_z1: 'THUDM/GLM-Z1-9B-0414',\n // glm_4_1: 'THUDM/GLM-4.1V-9B-Thinking',\n // ds_r1_qwen: 'deepseek-ai/DeepSeek-R1-Distill-Qwen-7B',\n // ds_r1_qwen3: 'deepseek-ai/DeepSeek-R1-0528-Qwen3-8B',\n // ds_r1_llama: 'deepseek-ai/DeepSeek-R1-Distill-Llama-8B',\n}\nexport async function siliconCloudTranslate(novelText = '', notsArr = [], aimd = 'glm', onRead = console.log) {\n try {\n if (!novelText.trim()) return\n novelText = replaceNovelMark(novelText)\n if (notsArr.length) {\n notsArr.forEach((e, i) => {\n novelText = novelText.replaceAll(e, `[名字${i}]`)\n })\n }\n const response = await fetch('https://api.siliconflow.cn/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'authorization': `Bearer ${SILICON_CLOUD_API_KEY}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify({\n model: aiModelMap[aimd],\n // temperature: aimd == 'glm' ? 0.01 : 0,\n stream: true,\n messages: [\n {\n role: 'system',\n // content: 'You are a professional, authentic machine translation engine.',\n // content: 'You are a highly skilled translation engine with expertise in eBook translation. Your function is to translate eBook texts accurately into the Simplified Chinese Language, maintaining the original tone, style, and formatting. Focus on delivering translations that resonate with the intended audience while ensuring the essence of the original text is preserved.',\n // content: 'You are a highly skilled translation engine with expertise in fiction literature. Your function is to translate texts into the Simplified Chinese Language, capturing the narrative depth and emotional nuances of the original work. Maintain the original storytelling elements and cultural references without adding any explanations or annotations.',\n // content: '你是一个专业的、正宗的机器翻译引擎。',\n content: 'You are a professional literary translator for Pixiv Japanese novels.',\n },\n {\n role: 'user',\n // content: `Translate the text starting on the next line into Simplified Chinese Language, output translation ONLY. NO explanations. NO notes. The content in \"「」\" also needs to be translated. Input:\\n${novelText}`,\n // content: `Translate the following source text to Simplified Chinese Language, Output translation directly without any additional text.\\nSource Text: ${novelText}`,\n // content: `将下面的文本翻译为简体中文,直接输出翻译结果,不附加其他文本。\\n${novelText}`,\n content: `Translate the following source text into Simplified Chinese Language. Ensure the translation is fluent and natural, maintaining the original meaning and style. Keep names, tone, pacing, and line intent consistent with the source. Do not censor or skip any content. Provide only the translation, without any explanation.\\nSource Text:\\n${novelText}`,\n },\n ],\n }),\n })\n const reader = response.body.getReader()\n const decoder = new TextDecoder()\n\n while (true) {\n const { value, done } = await reader.read()\n if (done) break\n const chunk = decoder.decode(value, { stream: true })\n const jsonLines = chunk.split('\\n').filter(line => line.trim() !== '')\n\n for (let jsonLine of jsonLines) {\n jsonLine = jsonLine.replace(/^data: /, '')\n if (jsonLine === '[DONE]') {\n onRead({ done: true })\n return\n }\n const json = JSON.parse(jsonLine)\n const { content, reasoning_content: reason } = json.choices[0].delta\n\n if (content) {\n onRead({ done: false, content })\n } else if (reason) {\n onRead({ done: false, content: reason, reasoning: true })\n }\n }\n }\n } catch (err) {\n console.log('siliconCloudTranslate err: ', err)\n }\n}\n\nfunction replaceNovelMark(text) {\n return text.replace(/\\[newpage\\]/g, '\\n\\n————————\\n\\n')\n .replace(/\\[\\[rb:([^>[\\]]+) *> *([^>[\\]]+)\\]\\]/g, '$1($2)')\n .replace(/\\[\\[jumpuri:([^>\\s[\\]]+) *> *([^>\\s[\\]]+)\\]\\]/g, '$1')\n .replace(/\\[pixivimage:([\\d-]+)\\]/g, 'https://pximg.cocomi.eu.org/-pid-/$1')\n .replace(/\\[chapter: *([^[\\]]+)\\]/g, '\\n$1\\n')\n .replace(/\\[uploadedimage:(\\d+)\\]/g, '')\n}\n\nexport const isNativeTranslatorSupported = 'Translator' in self && !navigator.userAgent.includes('Edg/')\nlet translator\nasync function ensureTranslator() {\n if (translator) return\n translator = await window.Translator.create({\n sourceLanguage: 'ja',\n targetLanguage: 'zh',\n })\n}\nexport async function nativeTranslate(novelText = '', onRead = () => {}) {\n try {\n if (!novelText.trim()) return\n novelText = replaceNovelMark(novelText)\n const textArr = novelText.split('\\n')\n await ensureTranslator()\n for (const text of textArr) {\n if (!text.trim()) continue\n const stream = translator.translateStreaming(text)\n for await (const chunk of stream) {\n console.log(chunk)\n onRead({ done: false, content: chunk })\n }\n onRead({ done: false, content: '\\n' })\n }\n onRead({ done: true })\n } catch (err) {\n console.log('nativeTranslate err: ', err)\n }\n}\n"}}}} |