Compare commits

...

8 Commits

Author SHA1 Message Date
箱庭XTer cd16f5b5fd
Merge 6c44f5b3fd into c52bdb9a4a 2024-05-17 17:59:32 +00:00
Cohee c52bdb9a4a Use new command names in examples 2024-05-17 20:59:00 +03:00
Cohee bbd9c89357 Add aliases for group member commands 2024-05-17 20:57:03 +03:00
Cohee fb2190ace1 #2254 Don't suppress abort in subcommands 2024-05-17 18:21:13 +03:00
Cohee deb09bf5bf Fix console errors on not found command autocomplete 2024-05-17 17:47:40 +03:00
Cohee d951beb626 #2260 Handle window resize in script editor 2024-05-17 17:47:18 +03:00
XTer 6c44f5b3fd 增加了提示性信息 2024-03-14 01:21:04 +08:00
XTer 42083b371b 添加了第一版GSVI的TTS适配 2024-03-14 00:36:56 +08:00
6 changed files with 312 additions and 14 deletions

View File

@ -342,6 +342,16 @@ export class QuickReply {
message.addEventListener('scroll', (evt)=>{
updateScrollDebounced();
});
/** @type {any} */
const resizeListener = debounce((evt) => {
updateSyntax();
updateScrollDebounced(evt);
if (document.activeElement == message) {
message.blur();
message.focus();
}
});
window.addEventListener('resize', resizeListener);
message.style.color = 'transparent';
message.style.background = 'transparent';
message.style.setProperty('text-shadow', 'none', 'important');
@ -514,6 +524,8 @@ export class QuickReply {
});
await popupResult;
window.removeEventListener('resize', resizeListener);
} else {
warn('failed to fetch qrEditor template');
}

View File

@ -0,0 +1,276 @@
import { saveTtsProviderSettings } from './index.js';
export { GSVITtsProvider };
class GSVITtsProvider {
//########//
// Config //
//########//
settings;
ready = false;
separator = '. ';
characterList = {};
voices = [];
/**
* Perform any text processing before passing to TTS engine.
* @param {string} text Input text
* @returns {string} Processed text
*/
processText(text) {
text = text.replace('<br>', '\n'); // Replace <br> with newline
return text;
}
languageLabels = {
'多语种混合': '多语种混合',
'中文': '中文',
'英文': '英文',
'日文': '日文',
'中英混合': '中英混合',
'日英混合': '日英混合',
};
defaultSettings = {
provider_endpoint: 'http://127.0.0.1:5000',
language: '多语种混合',
cha_name: '',
character_emotion: 'default',
speed: 1,
top_k: 6,
top_p: 0.85,
temperature: 0.75,
batch_size: 10,
stream: false,
stream_chunk_size: 100,
};
// 新增获取角色和情绪的方法
async fetchCharacterList() {
const response = await fetch(this.settings.provider_endpoint + '/character_list');
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
const characterList = await response.json();
this.characterList = characterList;
this.voices = Object.keys(characterList);
}
get settingsHtml() {
let html = `
<label for="gsvi_api_language">Text Language</label>
<select id="gsvi_api_language">`;
for (let language in this.languageLabels) {
if (this.languageLabels[language] == this.settings?.language) {
html += `<option value="${this.languageLabels[language]}" selected="selected">${language}</option>`;
continue;
}
html += `<option value="${this.languageLabels[language]}">${language}</option>`;
}
html += `
</select>
<label>GSVI Settings:</label><br/>
<label for="gsvi_tts_endpoint">Provider Endpoint:</label>
<input id="gsvi_tts_endpoint" type="text" class="text_pole" maxlength="250" value="${this.defaultSettings.provider_endpoint}"/>
<div>Goto <a target="_blank" href="https://www.yuque.com/xter/zibxlp/knu8p82lb5ipufqy">API Document</a>.</div>
<label for="gsvi_speed">Speed: <span id="gsvi_tts_speed_output">${this.defaultSettings.speed}</span></label>
<input id="gsvi_speed" type="range" value="${this.defaultSettings.speed}" min="0.5" max="2" step="0.01" />
<label for="gsvi_top_k">Top K: <span id="gsvi_top_k_output">${this.defaultSettings.top_k}</span></label>
<input id="gsvi_top_k" type="range" value="${this.defaultSettings.top_k}" min="0" max="100" step="1" />
<label for="gsvi_top_p">Top P: <span id="gsvi_top_p_output">${this.defaultSettings.top_p}</span></label>
<input id="gsvi_top_p" type="range" value="${this.defaultSettings.top_p}" min="0" max="1" step="0.01" />
<label for="gsvi_temperature">Temperature: <span id="gsvi_tts_temperature_output">${this.defaultSettings.temperature}</span></label>
<input id="gsvi_temperature" type="range" value="${this.defaultSettings.temperature}" min="0.01" max="1" step="0.01" />
<label for="gsvi_batch_size">Batch Size: <span id="gsvi_batch_size_output">${this.defaultSettings.batch_size}</span></label>
<input id="gsvi_batch_size" type="range" value="${this.defaultSettings.batch_size}" min="1" max="35" step="1" />
<label for="gsvi_tts_streaming" class="checkbox_label">
<input id="gsvi_tts_streaming" type="checkbox" ${this.defaultSettings.stream ? 'checked' : ''}/>
<span>Streaming</span>
</label>
<label for="gsvi_stream_chunk_size">Stream Chunk Size: <span id="gsvi_stream_chunk_size_output">${this.defaultSettings.stream_chunk_size}</span></label>
<input id="gsvi_stream_chunk_size" type="range" value="${this.defaultSettings.stream_chunk_size}" min="100" max="400" step="1" />
<title>About GSVI (GPT-Sovits Inference)</title>
<p>
GSVI (GPT-Sovits Inference) is an inference enhancement project based on
<a href="https://github.com/RVC-Boss/GPT-SoVITS" target="_blank">GPT-Sovits</a>, allowing you to run an API interface locally, offering emotion-rich speech-to-text and convenient model management features.
</p>
<p>
For more information, visit the
<a href="https://github.com/X-T-E-R/GPT-SoVITS-Inference" target="_blank">GSVI project page</a>.
</p>
`;
return html;
}
onSettingsChange() {
// Update provider settings based on input fields
this.settings.provider_endpoint = $('#gsvi_tts_endpoint').val();
this.settings.language = $('#gsvi_api_language').val();
// Update the rest of TTS settings based on input fields
this.settings.speed = parseFloat($('#gsvi_speed').val());
this.settings.temperature = parseFloat($('#gsvi_temperature').val());
this.settings.top_k = parseInt($('#gsvi_top_k').val(), 10);
this.settings.top_p = parseFloat($('#gsvi_top_p').val());
this.settings.batch_size = parseInt($('#gsvi_batch_size').val(), 10);
this.settings.stream = $('#gsvi_tts_streaming').is(':checked');
this.settings.stream_chunk_size = parseInt($('#gsvi_stream_chunk_size').val(), 10);
// Update UI to reflect changes
$('#gsvi_tts_speed_output').text(this.settings.speed);
$('#gsvi_tts_temperature_output').text(this.settings.temperature);
$('#gsvi_top_k_output').text(this.settings.top_k);
$('#gsvi_top_p_output').text(this.settings.top_p);
$('#gsvi_stream_chunk_size_output').text(this.settings.stream_chunk_size);
$('#gsvi_batch_size_output').text(this.settings.batch_size);
// Persist settings changes
saveTtsProviderSettings();
}
async loadSettings(settings) {
// Populate Provider UI given input settings
if (Object.keys(settings).length === 0) {
console.info('Using default TTS Provider settings');
}
// Only accept keys defined in defaultSettings
this.settings = { ...this.defaultSettings, ...settings };
// Fetch character and emotion list
// Set initial values from the settings
$('#gsvi_tts_endpoint').val(this.settings.provider_endpoint);
$('#gsvi_api_language').val(this.settings.language);
$('#gsvi_speed').val(this.settings.speed);
$('#gsvi_temperature').val(this.settings.temperature);
$('#gsvi_top_k').val(this.settings.top_k);
$('#gsvi_top_p').val(this.settings.top_p);
$('#gsvi_batch_size').val(this.settings.batch_size);
$('#gsvi_tts_streaming').prop('checked', this.settings.stream);
$('#gsvi_stream_chunk_size').val(this.settings.stream_chunk_size);
// Update UI to reflect initial settings
$('#gsvi_tts_speed_output').text(this.settings.speed);
$('#gsvi_tts_temperature_output').text(this.settings.temperature);
$('#gsvi_top_k_output').text(this.settings.top_k);
$('#gsvi_top_p_output').text(this.settings.top_p);
$('#gsvi_stream_chunk_size_output').text(this.settings.stream_chunk_size);
// Register event listeners to update settings on user interaction
// (Similar to before, ensure event listeners for character and emotion selection are included)
// Register input/change event listeners to update settings on user interaction
$('#gsvi_tts_endpoint').on('input', () => { this.onSettingsChange(); });
$('#gsvi_api_language').on('change', () => { this.onSettingsChange(); });
$('#gsvi_speed').on('input', () => { this.onSettingsChange(); });
$('#gsvi_temperature').on('input', () => { this.onSettingsChange(); });
$('#gsvi_top_k').on('input', () => { this.onSettingsChange(); });
$('#gsvi_top_p').on('input', () => { this.onSettingsChange(); });
$('#gsvi_batch_size').on('input', () => { this.onSettingsChange(); });
$('#gsvi_tts_streaming').on('change', () => { this.onSettingsChange(); });
$('#gsvi_stream_chunk_size').on('input', () => { this.onSettingsChange(); });
await this.checkReady();
console.debug('GSVI: Settings loaded');
}
// Perform a simple readiness check by trying to fetch voiceIds
async checkReady() {
await Promise.allSettled([this.fetchCharacterList()]);
}
async onRefreshClick() {
return;
}
//#################//
// TTS Interfaces //
//#################//
async getVoice(voiceName) {
if (this.voices.length == 0) {
this.fetchCharacterList();
}
if (!this.voices.includes(voiceName)) {
throw `TTS Voice name ${voiceName} not found`;
}
return { name: voiceName, voice_id: voiceName, preview_url: false, lang: 'zh-CN' };
}
async generateTts(text, voiceId) {
const response = await this.fetchTtsGeneration(text, voiceId);
return response;
}
//###########//
// API CALLS //
//###########//
async fetchTtsVoiceObjects() {
if (this.voices.length == 0) {
await this.fetchCharacterList();
}
console.log(this.voices);
const voices = this.voices.map(x => ({ name: x, voice_id: x, preview_url: false, lang: 'zh-CN' }));
return voices;
}
async fetchTtsGeneration(inputText, voiceId) {
console.info(`Generating new TTS for voice_id ${voiceId}`);
const params = new URLSearchParams();
params.append('text', inputText);
params.append('cha_name', voiceId);
params.append('text_language', this.settings.language);
params.append('batch_size', this.settings.batch_size.toString());
params.append('speed', this.settings.speed.toString());
params.append('top_k', this.settings.top_k.toString());
params.append('top_p', this.settings.top_p.toString());
params.append('temperature', this.settings.temperature.toString());
params.append('stream', this.settings.stream.toString());
return `${this.settings.provider_endpoint}/tts?${params.toString()}`;
}
// Interface not used by GSVI TTS
async fetchTtsFromHistory(history_item_id) {
return Promise.resolve(history_item_id);
}
}

View File

@ -10,6 +10,7 @@ import { NovelTtsProvider } from './novel.js';
import { power_user } from '../../power-user.js';
import { OpenAITtsProvider } from './openai.js';
import { XTTSTtsProvider } from './xtts.js';
import { GSVITtsProvider } from './gsvi.js';
import { AllTalkTtsProvider } from './alltalk.js';
import { SpeechT5TtsProvider } from './speecht5.js';
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
@ -74,6 +75,7 @@ const ttsProviders = {
ElevenLabs: ElevenLabsTtsProvider,
Silero: SileroTtsProvider,
XTTSv2: XTTSTtsProvider,
GSVI: GSVITtsProvider,
System: SystemTtsProvider,
Coqui: CoquiTtsProvider,
Edge: EdgeTtsProvider,

View File

@ -447,8 +447,9 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'unhide',
],
helpString: 'Unhides a message from the prompt.',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'disable',
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'member-disable',
callback: disableGroupMemberCallback,
aliases: ['disable', 'disablemember', 'memberdisable'],
unnamedArgumentList: [
new SlashCommandArgument(
'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true,
@ -456,7 +457,8 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'disable',
],
helpString: 'Disables a group member from being drafted for replies.',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'enable',
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'member-enable',
aliases: ['enable', 'enablemember', 'memberenable'],
callback: enableGroupMemberCallback,
unnamedArgumentList: [
new SlashCommandArgument(
@ -465,9 +467,9 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'enable',
],
helpString: 'Enables a group member to be drafted for replies.',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'memberadd',
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'member-add',
callback: addGroupMemberCallback,
aliases: ['addmember'],
aliases: ['addmember', 'memberadd'],
unnamedArgumentList: [
new SlashCommandArgument(
'character name', [ARGUMENT_TYPE.STRING], true,
@ -481,15 +483,15 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'memberadd',
<strong>Example:</strong>
<ul>
<li>
<pre><code>/memberadd John Doe</code></pre>
<pre><code>/member-add John Doe</code></pre>
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'memberremove',
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'member-remove',
callback: removeGroupMemberCallback,
aliases: ['removemember'],
aliases: ['removemember', 'memberremove'],
unnamedArgumentList: [
new SlashCommandArgument(
'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true,
@ -503,16 +505,16 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'memberremove
<strong>Example:</strong>
<ul>
<li>
<pre><code>/memberremove 2</code></pre>
<pre><code>/memberremove John Doe</code></pre>
<pre><code>/member-remove 2</code></pre>
<pre><code>/member-remove John Doe</code></pre>
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'memberup',
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'member-up',
callback: moveGroupMemberUpCallback,
aliases: ['upmember'],
aliases: ['upmember', 'memberup'],
unnamedArgumentList: [
new SlashCommandArgument(
'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true,
@ -520,9 +522,9 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'memberup',
],
helpString: 'Moves a group member up in the group chat list.',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'memberdown',
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'member-down',
callback: moveGroupMemberDownCallback,
aliases: ['downmember'],
aliases: ['downmember', 'memberdown'],
unnamedArgumentList: [
new SlashCommandArgument(
'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true,

View File

@ -58,6 +58,9 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
return new RegExp('=(.*)');
}
}
if (!Array.isArray(this.executor.command?.namedArgumentList)) {
return null;
}
const notProvidedNamedArguments = this.executor.command.namedArgumentList.filter(arg=>!this.executor.namedArgumentList.find(it=>it.name == arg.name));
let name;
let value;
@ -130,6 +133,9 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
}
getUnnamedArgumentAt(text, index, isSelect) {
if (!Array.isArray(this.executor.command?.unnamedArgumentList)) {
return null;
}
const lastArgIsBlank = this.executor.unnamedArgumentList.slice(-1)[0]?.value == '';
const notProvidedArguments = this.executor.command.unnamedArgumentList.slice(this.executor.unnamedArgumentList.length - (lastArgIsBlank ? 1 : 0));
let value;

View File

@ -518,7 +518,7 @@ async function executeSubCommands(command, scope = null, parserFlags = null) {
command = command.slice(1, -1);
}
const result = await executeSlashCommands(command, true, scope, true, parserFlags);
const result = await executeSlashCommands(command, true, scope, false, parserFlags);
if (!result || typeof result !== 'object') {
return '';