From 104729558f4209b6b7c61265f5715e221f27acc8 Mon Sep 17 00:00:00 2001 From: fox3000foxy Date: Thu, 20 Feb 2025 15:09:00 +0100 Subject: [PATCH] CustomVoiceFilter: Refactor WikiHomeModal with improved structure and Markdown support --- src/plugins/customVoiceFilter/Markdown.tsx | 107 ++++++++++++++++++ .../customVoiceFilter/WikiHomeModal.tsx | 105 +++++++++-------- src/plugins/customVoiceFilter/index.tsx | 2 + src/plugins/customVoiceFilter/style.css | 41 +++++++ 4 files changed, 208 insertions(+), 47 deletions(-) create mode 100644 src/plugins/customVoiceFilter/Markdown.tsx create mode 100644 src/plugins/customVoiceFilter/style.css diff --git a/src/plugins/customVoiceFilter/Markdown.tsx b/src/plugins/customVoiceFilter/Markdown.tsx new file mode 100644 index 000000000..ee37dcfa2 --- /dev/null +++ b/src/plugins/customVoiceFilter/Markdown.tsx @@ -0,0 +1,107 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { proxyLazy } from "@utils/lazy"; +import { findByCode, findByProps, findByPropsLazy } from "@webpack"; +import { Parser } from "@webpack/common"; + +import { openCreateVoiceModal } from "./CreateVoiceFilterModal"; +import { openHelpModal } from "./HelpModal"; +import { openVoiceFiltersModal } from "./VoiceFiltersModal"; + +interface MarkdownRules { + allowDevLinks: boolean; + allowEmojiLinks: boolean; + allowHeading: boolean; + allowLinks: boolean; + allowList: boolean; + channelId: string; + disableAnimatedEmoji: boolean; + disableAutoBlockNewlines: boolean; + forceWhite: boolean; + formatInline: boolean; + isInteracting: boolean; + mentionChannels: string[]; + messageId: string; + muted: boolean; + noStyleAndInteraction: boolean; + previewLinkTarget: boolean; + soundboardSounds: string[]; + unknownUserMentionPlaceholder: boolean; + viewingChannelId: string; +} + +const defaultRules: Partial = { allowLinks: true, allowList: true, allowHeading: true }; + +const MarkdownContainerClasses = findByPropsLazy("markup", "codeContainer"); +const modalLinkRegex = /^/; +const imageRegex = /^!\[((?:\[[^\]]*\]|[^[\]]|\](?=[^[]*\]))*)\]\(\s*((?:\([^)]*\)|[^\s\\]|\\.)*?)\)/; + +const modals: Record string, name: string; }> = { + help: { + action: openHelpModal, + name: "Help menu" + }, + createVoice: { + action: () => openCreateVoiceModal(), + name: "Voice pack creator menu" + }, + main: { + action: openVoiceFiltersModal, + name: "Main menu" + } +}; + +const parser: typeof Parser.parse = proxyLazy(() => { + const DiscordRules = findByProps("AUTO_MODERATION_SYSTEM_MESSAGE_RULES").RULES; + const AdvancedRules = findByCode("channelMention:")({}); + + const customRules = { + modalLink: { + order: DiscordRules.staticRouteLink, + match: source => modalLinkRegex.exec(source), + parse: ([, target]) => (modals[target]), + react: ({ action, name }) => ( + {name} + ), + requiredFirstCharacters: ["<"] + }, + image: { + ...Parser.defaultRules.link, + match: source => imageRegex.exec(source), + parse: ([, title, target]) => ({ title, target }), + react: ({ title, target }) =>
+ {title} +
, + requiredFirstCharacters: ["!"] + } + }; + + const builtinRules = new Set([...Object.keys(DiscordRules), ...Object.keys(AdvancedRules)]); + + for (const rule of builtinRules) { + customRules[rule] = { + ...DiscordRules[rule], + ...AdvancedRules[rule], + }; + } + + console.log(customRules); + + return (Parser as any).reactParserFor(customRules); +}); + +interface MarkdownProps { + content: string; + markdownRules?: Partial; +} + + +export function Markdown({ content, markdownRules = defaultRules }: MarkdownProps) { + return
+ {parser(content, false, markdownRules)} +
; +} diff --git a/src/plugins/customVoiceFilter/WikiHomeModal.tsx b/src/plugins/customVoiceFilter/WikiHomeModal.tsx index da538f944..4590e0302 100644 --- a/src/plugins/customVoiceFilter/WikiHomeModal.tsx +++ b/src/plugins/customVoiceFilter/WikiHomeModal.tsx @@ -5,11 +5,47 @@ */ import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; -import { Button, Card, Forms, Text, useState } from "@webpack/common"; +import { Button, Card, Flex, Forms, Text, useState } from "@webpack/common"; -import { openCreateVoiceModal } from "./CreateVoiceFilterModal"; -import { openHelpModal } from "./HelpModal"; -import { openVoiceFiltersModal } from "./VoiceFiltersModal"; +import { Markdown } from "./Markdown"; + +interface Section { + title: string; + content: string; +} + +const sections: Section[] = [ + { + title: "How to install a voicepack", + content: "To install a voicepack, you need to paste the voicepack url in the " + }, + { + title: "How to create a voicepack", + content: `You have two methods to create a voicepack: +1. Use the (recommended) +2. Use the (advanced)` + }, + { + title: "How does it work?", + content: `Discord actually uses a Python project named [Retrieval-based Voice Conversion](https://github.com/RVC-Project/Retrieval-based-Voice-Conversion) to convert your voice into the voice model you picked. +This voice cloning technology allows an audio input to be converted into a different voice, with a high degree of accuracy. +Actually, Discord uses ONNX files to run the model, for a better performance and less CPU usage. +![img](https://fox3000foxy.com/voicepacks/assets/working.png)` + }, + { + title: "How to create an ONNX from an existing RVC model?", + content: `RVC models can be converted to ONNX files using the [W-Okada Software](https://github.com/w-okada/voice-changer/). +MMVCServerSio is software that is issued from W-Okada Software, and can be downloaded [here](https://huggingface.co/datasets/Derur/all-portable-ai-in-one-url/blob/main/HZ/MMVCServerSIO.7z). +Thats the actual software that does exports RVC models to ONNX files. +Just load your model inside MMVCServerSio, and click on "Export ONNX": +![img](https://fox3000foxy.com/voicepacks/assets/export-1.png)![img](https://fox3000foxy.com/voicepacks/assets/export-2.png) +Enjoy you now have a ONNX model file for your voicepack!` + }, + { + title: "How to train my own voice model?", + content: "Refers to [this video](https://www.youtube.com/watch?v=tnfqIQ11Qek&ab_channel=AISearch) and convert it to ONNX." + } +]; interface WikiHomeModalProps { modalProps: ModalProps; @@ -19,51 +55,21 @@ interface WikiHomeModalProps { export function WikiHomeModal({ modalProps, close, accept }: WikiHomeModalProps) { return ( - + Wiki Home - -

- Here are some tutorials and guides about the Custom Voice Filter Plugin: -

- To install a voicepack, you need to paste the voicepack url in the openVoiceFiltersModal()}>main menu. - } />
- - You have two methods to create a voicepack:
- 1. Use the openCreateVoiceModal()}>voicepack creator modal (recommended)
- 2. Use the openHelpModal()}>Help Modal (advanced) - - } />
- - Discord actually uses a Python project named Retrieval-based Voice Conversion
to convert your voice into the voice model you picked.

- This voice cloning technology allows an audio input to be converted into a different voice, with a high degree of accuracy.
- Actually, Discord uses ONNX files to run the model, for a better performance and less CPU usage.
- - - } />
- - RVC models can be converted to ONNX files using the W-Okada Software.
- MMVCServerSio is software that is issued from W-Okada Software, and can be downloaded here.
- Thats the actual software that does exports RVC models to ONNX files.
- Just load your model inside MMVCServerSio, and click on "Export ONNX":
-
-
- Enjoy you now have a ONNX model file for your voicepack! - - } />
- - Refers to this video and convert it to ONNX. - - } />
+ + + Here are some tutorials and guides about the Custom Voice Filter Plugin: + + {sections.map((section, index) => ( + + ))} + @@ -86,15 +92,20 @@ export function openWikiHomeModal(): string { return key; } -function CollapsibleCard({ title, content }: { title: string; content: React.ReactNode; }) { +interface CollapsibleCardProps { + title: string; + content: string; +} + +function CollapsibleCard({ title, content }: CollapsibleCardProps) { const [isOpen, setIsOpen] = useState(false); return ( - - setIsOpen(!isOpen)} style={{ cursor: "pointer", background: "var(--background-primary)", padding: "10px", marginBottom: isOpen ? "10px" : "0px" }}> + + setIsOpen(!isOpen)} style={{ cursor: "pointer", background: "var(--background-primary)", padding: "10px" }}> {title} - {isOpen && {content}} + {isOpen && } ); } diff --git a/src/plugins/customVoiceFilter/index.tsx b/src/plugins/customVoiceFilter/index.tsx index 55d482fec..762b68d3c 100644 --- a/src/plugins/customVoiceFilter/index.tsx +++ b/src/plugins/customVoiceFilter/index.tsx @@ -5,6 +5,8 @@ */ // Imports +import "./style.css"; + import { DataStore } from "@api/index"; import { Devs } from "@utils/constants"; import { proxyLazy } from "@utils/lazy"; diff --git a/src/plugins/customVoiceFilter/style.css b/src/plugins/customVoiceFilter/style.css new file mode 100644 index 000000000..288442c9c --- /dev/null +++ b/src/plugins/customVoiceFilter/style.css @@ -0,0 +1,41 @@ +.vc-voice-filters-wiki { + max-width: var(--modal-width-large); +} + +.vc-voice-filters-md { + display: flex; + flex-direction: column; + align-items: stretch; + padding-inline: 1.2rem; +} + +.vc-voice-filters-md-image { + width: 100%; + max-height: 20rem; + overflow: hidden; + display: flex; + margin-block: 0.5rem; + + img { + width: 100%; + background: var(--background-tertiary); + border-radius: 0.5rem; + object-fit: contain; + } +} + +.vc-voice-filters-card { + background: var(--background-secondary); + width: 100%; +} + +.vc-voice-filters-modal-link { + border-radius: 3px; + padding: 0 2px; + font-weight: 500; + unicode-bidi: plaintext; + color: var(--mention-foreground); + background: var(--mention-background); + text-overflow: ellipsis; + overflow: hidden; +}