mirror of
https://github.com/Vendicated/Vencord.git
synced 2025-02-24 15:35:11 +00:00
CustomVoiceFilter: Refactor WikiHomeModal with improved structure and Markdown support
This commit is contained in:
parent
fe417ace18
commit
104729558f
4 changed files with 208 additions and 47 deletions
107
src/plugins/customVoiceFilter/Markdown.tsx
Normal file
107
src/plugins/customVoiceFilter/Markdown.tsx
Normal 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>;
|
||||||
|
}
|
|
@ -5,11 +5,47 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
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 { Markdown } from "./Markdown";
|
||||||
import { openHelpModal } from "./HelpModal";
|
|
||||||
import { openVoiceFiltersModal } from "./VoiceFiltersModal";
|
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.
|
||||||
|
`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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":
|
||||||
|

|
||||||
|
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 {
|
interface WikiHomeModalProps {
|
||||||
modalProps: ModalProps;
|
modalProps: ModalProps;
|
||||||
|
@ -19,51 +55,21 @@ interface WikiHomeModalProps {
|
||||||
|
|
||||||
export function WikiHomeModal({ modalProps, close, accept }: WikiHomeModalProps) {
|
export function WikiHomeModal({ modalProps, close, accept }: WikiHomeModalProps) {
|
||||||
return (
|
return (
|
||||||
<ModalRoot {...modalProps} size={ModalSize.LARGE}>
|
<ModalRoot {...modalProps} size={ModalSize.LARGE} className="vc-voice-filters-wiki">
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
<Forms.FormTitle tag="h2" className="modalTitle">
|
<Forms.FormTitle tag="h2" className="modalTitle">
|
||||||
Wiki Home
|
Wiki Home
|
||||||
</Forms.FormTitle>
|
</Forms.FormTitle>
|
||||||
<ModalCloseButton onClick={close} />
|
<ModalCloseButton onClick={close} />
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<ModalContent>
|
<ModalContent style={{ paddingBlock: "0.5rem" }}>
|
||||||
<br /><br />
|
<Flex style={{ gap: "0.5rem" }} direction={Flex.Direction.VERTICAL}>
|
||||||
<Text>Here are some tutorials and guides about the Custom Voice Filter Plugin:</Text>
|
<Text>Here are some tutorials and guides about the Custom Voice Filter Plugin:</Text>
|
||||||
<br /><br />
|
|
||||||
<CollapsibleCard title="How to install a voicepack" content={
|
{sections.map((section, index) => (
|
||||||
<Text>To install a voicepack, you need to paste the voicepack url in the <a onClick={() => openVoiceFiltersModal()}>main menu</a>.</Text>
|
<CollapsibleCard key={index} title={section.title} content={section.content} />
|
||||||
} /><br />
|
))}
|
||||||
<CollapsibleCard title="How to create a voicepack" content={
|
</Flex>
|
||||||
<>
|
|
||||||
<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 />
|
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button onClick={close}>Close</Button>
|
<Button onClick={close}>Close</Button>
|
||||||
|
@ -86,15 +92,20 @@ export function openWikiHomeModal(): string {
|
||||||
return key;
|
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);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card style={{ background: "var(--background-secondary)", width: "100%" }}>
|
<Card className="vc-voice-filters-card">
|
||||||
<Card onClick={() => setIsOpen(!isOpen)} style={{ cursor: "pointer", background: "var(--background-primary)", padding: "10px", marginBottom: isOpen ? "10px" : "0px" }}>
|
<Card onClick={() => setIsOpen(!isOpen)} style={{ cursor: "pointer", background: "var(--background-primary)", padding: "10px" }}>
|
||||||
<Text style={{ fontSize: "18px", fontWeight: "bold" }}>{title}</Text>
|
<Text style={{ fontSize: "18px", fontWeight: "bold" }}>{title}</Text>
|
||||||
</Card>
|
</Card>
|
||||||
{isOpen && <Text style={{ padding: "10px", paddingTop: "0px" }}>{content}</Text>}
|
{isOpen && <Markdown content={content} />}
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Imports
|
// Imports
|
||||||
|
import "./style.css";
|
||||||
|
|
||||||
import { DataStore } from "@api/index";
|
import { DataStore } from "@api/index";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { proxyLazy } from "@utils/lazy";
|
import { proxyLazy } from "@utils/lazy";
|
||||||
|
|
41
src/plugins/customVoiceFilter/style.css
Normal file
41
src/plugins/customVoiceFilter/style.css
Normal 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;
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue