diff --git a/package.json b/package.json index c1f94594..c280a80d 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "dependencies": { "@adobe/css-tools": "4.3.2", "@aws-crypto/sha256-js": "^5.2.0", + "@breezystack/lamejs": "^1.2.7", "@capacitor/android": "^5.6.0", "@capacitor/core": "^5.6.0", "@capacitor/filesystem": "^5.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8bb3a889..737a7b4c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@aws-crypto/sha256-js': specifier: ^5.2.0 version: 5.2.0 + '@breezystack/lamejs': + specifier: ^1.2.7 + version: 1.2.7 '@capacitor/android': specifier: ^5.6.0 version: 5.6.0(@capacitor/core@5.6.0) @@ -361,6 +364,9 @@ packages: resolution: {integrity: sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==} engines: {node: '>=6.9.0'} + '@breezystack/lamejs@1.2.7': + resolution: {integrity: sha512-6wc7ck65ctA75Hq7FYHTtTvGnYs6msgdxiSUICQ+A01nVOWg6rqouZB8IdyteRlfpYYiFovkf67dIeOgWIUzTA==} + '@capacitor/android@5.6.0': resolution: {integrity: sha512-6O7xV6K6c8WvQzKxOe7fnhRyoVpS3TNDXy1FyfhvOvclBvu+1JddSdFvW4e4dSL60s2c00sCzNRgYhm+cn0/dQ==} peerDependencies: @@ -4061,6 +4067,8 @@ snapshots: chalk: 2.4.2 js-tokens: 4.0.0 + '@breezystack/lamejs@1.2.7': {} + '@capacitor/android@5.6.0(@capacitor/core@5.6.0)': dependencies: '@capacitor/core': 5.6.0 diff --git a/src/App.svelte b/src/App.svelte index 5f1d529c..b2a760df 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -36,7 +36,7 @@ await importCharacterProcess({ name: file.name, data: file - }) + }) checkCharOrder() } }}> diff --git a/src/lang/en.ts b/src/lang/en.ts index ae61f590..aaba8e64 100644 --- a/src/lang/en.ts +++ b/src/lang/en.ts @@ -823,4 +823,8 @@ export const languageEnglish = { presetChain: "Preset Chain", legacyMediaFindings: "Legacy Media Findings", staticsDisclaimer: "The statistics are based on the data from after July 2024. the data may not be accurate.", + subtitles: "Subtitles", + subtitlesWarning1: "You must use model with audio/video input to use this feature.", + subtitlesWarning2: "You must use model with streaming feature to use this feature.", + reset: "Reset", } \ No newline at end of file diff --git a/src/lib/Playground/PlaygroundMenu.svelte b/src/lib/Playground/PlaygroundMenu.svelte index ac7756a3..3d007539 100644 --- a/src/lib/Playground/PlaygroundMenu.svelte +++ b/src/lib/Playground/PlaygroundMenu.svelte @@ -14,6 +14,7 @@ import PlaygroundParser from "./PlaygroundParser.svelte"; import ToolConvertion from "./ToolConvertion.svelte"; import { joinMultiuserRoom } from "src/ts/sync/multiuser"; + import PlaygroundSubtitle from "./PlaygroundSubtitle.svelte"; let easterEggTouch = $state(0) @@ -83,6 +84,11 @@ }}>

Parser

+ +{:else if vttB64 && fileB64} +
+
{outputText}
+
+{:else} +
{outputText}
+{/if} + +{#if vttB64 && fileB64} +
+ {#key vttB64} + + {/key} +
+ + {language.download} + + + + +{/if} \ No newline at end of file diff --git a/src/ts/observer.ts b/src/ts/observer.ts index a40f870c..b868cb9d 100644 --- a/src/ts/observer.ts +++ b/src/ts/observer.ts @@ -8,6 +8,7 @@ function nodeObserve(node:HTMLElement){ const triggerName = node.getAttribute('risu-trigger'); const btnEvent = node.getAttribute('risu-btn'); const observerAdded = node.getAttribute('risu-observer'); + const hlLang = node.getAttribute('x-hl-lang'); if(observerAdded){ return @@ -45,13 +46,65 @@ function nodeObserve(node:HTMLElement){ node.setAttribute('risu-observer', 'true'); return } + + if(hlLang){ + node.addEventListener('contextmenu', (e)=>{ + e.preventDefault(); + const menu = document.createElement('div'); + menu.setAttribute('class', 'fixed z-50 min-w-[160px] py-2 bg-gray-800 rounded-lg border border-gray-700') + + const copyOption = document.createElement('div'); + copyOption.textContent = 'Copy'; + copyOption.setAttribute('class', 'px-4 py-2 text-sm text-gray-300 hover:bg-gray-700 cursor-pointer') + copyOption.addEventListener('click', ()=>{ + navigator.clipboard.writeText(node.getAttribute('x-hl-text')); + menu.remove(); + }) + + const downloadOption = document.createElement('div'); + downloadOption.textContent = 'Download'; + downloadOption.setAttribute('class', 'px-4 py-2 text-sm text-gray-300 hover:bg-gray-700 cursor-pointer') + downloadOption.addEventListener('click', ()=>{ + const a = document.createElement('a'); + a.href = URL.createObjectURL(new Blob([node.getAttribute('x-hl-text')], {type: 'text/plain'})); + a.download = 'code.' + hlLang; + a.click(); + menu.remove(); + }) + + menu.appendChild(copyOption); + menu.appendChild(downloadOption); + + menu.style.left = e.clientX + 'px'; + menu.style.top = e.clientY + 'px'; + + document.body.appendChild(menu); + + document.addEventListener('click', ()=>{ + menu.remove(); + }, {once: true}) + }) + } } export async function startObserveDom(){ + //For codeblock we are using MutationObserver since it doesn't appear well + + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if(node instanceof HTMLElement){ + nodeObserve(node); + } + }) + }) + }) + //We are using a while loop intead of MutationObserver because MutationObserver is expensive for just a few elements while(true){ document.querySelectorAll('[risu-trigger]').forEach(nodeObserve); document.querySelectorAll('[risu-btn]').forEach(nodeObserve); + document.querySelectorAll('[x-hl-lang]').forEach(nodeObserve); await sleep(100); } } \ No newline at end of file diff --git a/src/ts/parser.svelte.ts b/src/ts/parser.svelte.ts index 48f8f3f5..03b56482 100644 --- a/src/ts/parser.svelte.ts +++ b/src/ts/parser.svelte.ts @@ -117,18 +117,30 @@ async function renderHighlightableMarkdown(data:string) { //import language if not already loaded //we do not refactor this to a function because we want to keep vite to only import the languages that are needed let languageModule:any = null + let shotLang = '' switch(lang){ case 'js': case 'javascript':{ lang = 'javascript' + shotLang = 'js' if(!hljs.getLanguage('javascript')){ languageModule = await import('highlight.js/lib/languages/javascript') } break } + case 'txt': + case 'vtt':{ + shotLang = lang + lang = 'plaintext' + if(!hljs.getLanguage('plaintext')){ + languageModule = await import('highlight.js/lib/languages/plaintext') + } + break + } case 'py': case 'python':{ lang = 'python' + shotLang = 'py' if(!hljs.getLanguage('python')){ languageModule = await import('highlight.js/lib/languages/python') } @@ -136,6 +148,7 @@ async function renderHighlightableMarkdown(data:string) { } case 'css':{ lang = 'css' + shotLang = 'css' if(!hljs.getLanguage('css')){ languageModule = await import('highlight.js/lib/languages/css') } @@ -144,6 +157,7 @@ async function renderHighlightableMarkdown(data:string) { case 'xml': case 'html':{ lang = 'xml' + shotLang = 'xml' if(!hljs.getLanguage('xml')){ languageModule = await import('highlight.js/lib/languages/xml') } @@ -151,6 +165,7 @@ async function renderHighlightableMarkdown(data:string) { } case 'lua':{ lang = 'lua' + shotLang = 'lua' if(!hljs.getLanguage('lua')){ languageModule = await import('highlight.js/lib/languages/lua') } @@ -158,6 +173,7 @@ async function renderHighlightableMarkdown(data:string) { } case 'dart':{ lang = 'dart' + shotLang = 'dart' if(!hljs.getLanguage('dart')){ languageModule = await import('highlight.js/lib/languages/dart') } @@ -165,6 +181,7 @@ async function renderHighlightableMarkdown(data:string) { } case 'java':{ lang = 'java' + shotLang = 'java' if(!hljs.getLanguage('java')){ languageModule = await import('highlight.js/lib/languages/java') } @@ -172,6 +189,7 @@ async function renderHighlightableMarkdown(data:string) { } case 'rust':{ lang = 'rust' + shotLang = 'rs' if(!hljs.getLanguage('rust')){ languageModule = await import('highlight.js/lib/languages/rust') } @@ -180,6 +198,7 @@ async function renderHighlightableMarkdown(data:string) { case 'c': case 'cpp':{ lang = 'cpp' + shotLang = 'cpp' if(!hljs.getLanguage('cpp')){ languageModule = await import('highlight.js/lib/languages/cpp') } @@ -188,6 +207,7 @@ async function renderHighlightableMarkdown(data:string) { case 'csharp': case 'cs':{ lang = 'csharp' + shotLang = 'cs' if(!hljs.getLanguage('csharp')){ languageModule = await import('highlight.js/lib/languages/csharp') } @@ -196,6 +216,7 @@ async function renderHighlightableMarkdown(data:string) { case 'ts': case 'typescript':{ lang = 'typescript' + shotLang = 'ts' if(!hljs.getLanguage('typescript')){ languageModule = await import('highlight.js/lib/languages/typescript') } @@ -203,6 +224,7 @@ async function renderHighlightableMarkdown(data:string) { } case 'json':{ lang = 'json' + shotLang = 'json' if(!hljs.getLanguage('json')){ languageModule = await import('highlight.js/lib/languages/json') } @@ -210,6 +232,7 @@ async function renderHighlightableMarkdown(data:string) { } case 'yaml':{ lang = 'yaml' + shotLang = 'yml' if(!hljs.getLanguage('yaml')){ languageModule = await import('highlight.js/lib/languages/yaml') } @@ -217,6 +240,7 @@ async function renderHighlightableMarkdown(data:string) { } case 'shell':{ lang = 'shell' + shotLang = 'sh' if(!hljs.getLanguage('shell')){ languageModule = await import('highlight.js/lib/languages/shell') } @@ -224,6 +248,7 @@ async function renderHighlightableMarkdown(data:string) { } case 'bash':{ lang = 'bash' + shotLang = 'sh' if(!hljs.getLanguage('bash')){ languageModule = await import('highlight.js/lib/languages/bash') } @@ -231,6 +256,7 @@ async function renderHighlightableMarkdown(data:string) { } default:{ lang = 'none' + shotLang = 'none' } } if(languageModule){ @@ -244,7 +270,9 @@ async function renderHighlightableMarkdown(data:string) { language: lang, ignoreIllegals: true }).value - rendered = rendered.replace(placeholder, `
${highlighted}
`) + rendered = rendered.replace(placeholder, `
${highlighted}
`) } } catch (error) { @@ -492,8 +520,8 @@ export async function ParseMarkdown( data = await renderHighlightableMarkdown(data) } return decodeStyle(DOMPurify.sanitize(data, { - ADD_TAGS: ["iframe", "style", "risu-style", "x-em"], - ADD_ATTR: ["allow", "allowfullscreen", "frameborder", "scrolling", "risu-btn", 'risu-trigger', 'risu-mark'], + ADD_TAGS: ["iframe", "style", "risu-style", "x-em",], + ADD_ATTR: ["allow", "allowfullscreen", "frameborder", "scrolling", "risu-btn", 'risu-trigger', 'risu-mark', 'x-hl-lang', 'x-hl-text'], })) } diff --git a/src/ts/process/request.ts b/src/ts/process/request.ts index 2942bf5d..adb27280 100644 --- a/src/ts/process/request.ts +++ b/src/ts/process/request.ts @@ -500,12 +500,12 @@ async function requestOpenAI(arg:RequestDataArgumentExtended):Promise 0 && chat.role === "user") { + let geminiParts: GeminiPart[] = []; + + geminiParts.push({ + text: chat.content, + }); + + for (const modal of chat.multimodals) { + if ( + (modal.type === "image" && arg.modelInfo.flags.includes(LLMFlags.hasImageInput)) || + (modal.type === "audio" && arg.modelInfo.flags.includes(LLMFlags.hasAudioInput)) || + (modal.type === "video" && arg.modelInfo.flags.includes(LLMFlags.hasVideoInput)) + ) { + const dataurl = modal.base64; + const base64 = dataurl.split(",")[1]; + const mediaType = dataurl.split(";")[0].split(":")[1]; + + geminiParts.push({ + inlineData: { + mimeType: mediaType, + data: base64, + } + }); + } + } + + reformatedChat.push({ + role: "USER", + parts: geminiParts, + }); + + } else if (prevChat?.role === qRole) { + reformatedChat[reformatedChat.length-1].parts[0].text += '\n' + chat.content + continue + } + else if(chat.role === 'system'){ + if(prevChat?.role === 'USER'){ + reformatedChat[reformatedChat.length-1].parts[0].text += '\nsystem:' + chat.content } else{ reformatedChat.push({ @@ -1405,78 +1441,22 @@ async function requestGoogleCloudVertex(arg:RequestDataArgumentExtended):Promise }) } } + + else if(chat.role === 'assistant' || chat.role === 'user'){ + reformatedChat.push({ + role: chat.role === 'user' ? 'USER' : 'MODEL', + parts: [{ + text: chat.content + }] + }) + } else{ - const prevChat = reformatedChat[reformatedChat.length-1] - const qRole = - chat.role === 'user' ? 'USER' : - chat.role === 'assistant' ? 'MODEL' : - chat.role - - if (chat.multimodals && chat.multimodals.length > 0 && chat.role === "user") { - let geminiParts: GeminiPart[] = []; - - geminiParts.push({ - text: chat.content, - }); - - for (const modal of chat.multimodals) { - if ( - (modal.type === "image" && arg.modelInfo.flags.includes(LLMFlags.hasImageInput)) || - (modal.type === "audio" && arg.modelInfo.flags.includes(LLMFlags.hasAudioInput)) || - (modal.type === "video" && arg.modelInfo.flags.includes(LLMFlags.hasVideoInput)) - ) { - const dataurl = modal.base64; - const base64 = dataurl.split(",")[1]; - const mediaType = dataurl.split(";")[0].split(":")[1]; - - geminiParts.push({ - inlineData: { - mimeType: mediaType, - data: base64, - } - }); - } - } - - reformatedChat.push({ - role: "USER", - parts: geminiParts, - }); - - } else if (prevChat.role === qRole) { - reformatedChat[reformatedChat.length-1].parts[0].text += '\n' + chat.content - continue - } - else if(chat.role === 'system'){ - if(prevChat.role === 'USER'){ - reformatedChat[reformatedChat.length-1].parts[0].text += '\nsystem:' + chat.content - } - else{ - reformatedChat.push({ - role: "USER", - parts: [{ - text: chat.role + ':' + chat.content - }] - }) - } - } - - else if(chat.role === 'assistant' || chat.role === 'user'){ - reformatedChat.push({ - role: chat.role === 'user' ? 'USER' : 'MODEL', - parts: [{ - text: chat.content - }] - }) - } - else{ - reformatedChat.push({ - role: "USER", - parts: [{ - text: chat.role + ':' + chat.content - }] - }) - } + reformatedChat.push({ + role: "USER", + parts: [{ + text: chat.role + ':' + chat.content + }] + }) } }