CustomVoiceFilter: Refactor WikiHomeModal with improved structure and Markdown support

This commit is contained in:
fox3000foxy 2025-02-20 15:09:00 +01:00
parent fe417ace18
commit 104729558f
4 changed files with 208 additions and 47 deletions

View file

@ -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<MarkdownRules> = { allowLinks: true, allowList: true, allowHeading: true };
const MarkdownContainerClasses = findByPropsLazy("markup", "codeContainer");
const modalLinkRegex = /^<vf:(help|createVoice|main)>/;
const imageRegex = /^!\[((?:\[[^\]]*\]|[^[\]]|\](?=[^[]*\]))*)\]\(\s*((?:\([^)]*\)|[^\s\\]|\\.)*?)\)/;
const modals: Record<string, { action: () => 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 }) => (
<span className="channelMention interactive vc-voice-filters-modal-link" role="link" onClick={action}>{name}</span>
),
requiredFirstCharacters: ["<"]
},
image: {
...Parser.defaultRules.link,
match: source => imageRegex.exec(source),
parse: ([, title, target]) => ({ title, target }),
react: ({ title, target }) => <div className="vc-voice-filters-md-image">
<img src={target} alt={title} />
</div>,
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<MarkdownRules>;
}
export function Markdown({ content, markdownRules = defaultRules }: MarkdownProps) {
return <div className={`${MarkdownContainerClasses.markup} vc-voice-filters-md`}>
{parser(content, false, markdownRules)}
</div>;
}

View file

@ -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 <vf:main>"
},
{
title: "How to create a voicepack",
content: `You have two methods to create a voicepack:
1. Use the <vf:createVoice> (recommended)
2. Use the <vf:help> (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 (
<ModalRoot {...modalProps} size={ModalSize.LARGE}>
<ModalRoot {...modalProps} size={ModalSize.LARGE} className="vc-voice-filters-wiki">
<ModalHeader>
<Forms.FormTitle tag="h2" className="modalTitle">
Wiki Home
</Forms.FormTitle>
<ModalCloseButton onClick={close} />
</ModalHeader>
<ModalContent>
<br /><br />
<ModalContent style={{ paddingBlock: "0.5rem" }}>
<Flex style={{ gap: "0.5rem" }} direction={Flex.Direction.VERTICAL}>
<Text>Here are some tutorials and guides about the Custom Voice Filter Plugin:</Text>
<br /><br />
<CollapsibleCard title="How to install a voicepack" content={
<Text>To install a voicepack, you need to paste the voicepack url in the <a onClick={() => openVoiceFiltersModal()}>main menu</a>.</Text>
} /><br />
<CollapsibleCard title="How to create a voicepack" content={
<>
<Text>You have two methods to create a voicepack:</Text><br />
<Text>1. Use the <a onClick={() => openCreateVoiceModal()}>voicepack creator modal</a> (recommended)</Text><br />
<Text>2. Use the <a onClick={() => openHelpModal()}>Help Modal</a> (advanced)</Text>
</>
} /><br />
<CollapsibleCard title="How does it work?" content={
<>
<Text>Discord actually uses a Python project named <a href="https://github.com/RVC-Project/Retrieval-based-Voice-Conversion">Retrieval-based Voice Conversion</a><br />to convert your voice into the voice model you picked.</Text><br />
<Text>This voice cloning technology allows an audio input to be converted into a different voice, with a high degree of accuracy.</Text><br />
<Text>Actually, Discord uses ONNX files to run the model, for a better performance and less CPU usage.</Text><br />
<img style={{ width: "100%" }} src="https://fox3000foxy.com/voicepacks/assets/working.png" alt="" />
</>
} /><br />
<CollapsibleCard title="How to create an ONNX from an existing RVC model?" content={
<>
<Text>RVC models can be converted to ONNX files using the <a href="https://github.com/w-okada/voice-changer/">W-Okada Software</a>.</Text><br />
<Text>MMVCServerSio is software that is issued from W-Okada Software, and can be downloaded <a href="https://huggingface.co/datasets/Derur/all-portable-ai-in-one-url/blob/main/HZ/MMVCServerSIO.7z">here</a>.</Text><br />
<Text>Thats the actual software that does exports RVC models to ONNX files.</Text><br />
<Text>Just load your model inside MMVCServerSio, and click on "Export ONNX":</Text><br />
<img src="https://fox3000foxy.com/voicepacks/assets/export-1.png" alt="" /><br />
<img src="https://fox3000foxy.com/voicepacks/assets/export-2.png" alt="" /><br />
<Text>Enjoy you now have a ONNX model file for your voicepack!</Text>
</>
} /><br />
<CollapsibleCard title="How to train my own voice model?" content={
<>
<Text>Refers to <a href="https://www.youtube.com/watch?v=tnfqIQ11Qek&ab_channel=AISearch">this video</a> and convert it to ONNX.</Text>
</>
} /><br />
{sections.map((section, index) => (
<CollapsibleCard key={index} title={section.title} content={section.content} />
))}
</Flex>
</ModalContent>
<ModalFooter>
<Button onClick={close}>Close</Button>
@ -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 (
<Card style={{ background: "var(--background-secondary)", width: "100%" }}>
<Card onClick={() => setIsOpen(!isOpen)} style={{ cursor: "pointer", background: "var(--background-primary)", padding: "10px", marginBottom: isOpen ? "10px" : "0px" }}>
<Card className="vc-voice-filters-card">
<Card onClick={() => setIsOpen(!isOpen)} style={{ cursor: "pointer", background: "var(--background-primary)", padding: "10px" }}>
<Text style={{ fontSize: "18px", fontWeight: "bold" }}>{title}</Text>
</Card>
{isOpen && <Text style={{ padding: "10px", paddingTop: "0px" }}>{content}</Text>}
{isOpen && <Markdown content={content} />}
</Card>
);
}

View file

@ -5,6 +5,8 @@
*/
// Imports
import "./style.css";
import { DataStore } from "@api/index";
import { Devs } from "@utils/constants";
import { proxyLazy } from "@utils/lazy";

View file

@ -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;
}