Add custom theme

This commit is contained in:
kwaroran
2024-11-02 14:28:28 +09:00
parent 864715df85
commit c0f088309f
6 changed files with 184 additions and 24 deletions

View File

@@ -164,6 +164,7 @@ export const languageEnglish = {
translatorNote: "Here, you can add a unique translation prompt for each character. This option only applies when using the Ax. model for translation. To apply it, include `{{slot::tnote}}` in the language settings. It doesn't work in group chats.", translatorNote: "Here, you can add a unique translation prompt for each character. This option only applies when using the Ax. model for translation. To apply it, include `{{slot::tnote}}` in the language settings. It doesn't work in group chats.",
groupInnerFormat: "This defines a format that is used in group chat for characters that isn't speaker. if it is not blank, it will use this format instead of the default format. if `Group Other Bot Role` is `assistant`, it will also be applied to the speaker.", groupInnerFormat: "This defines a format that is used in group chat for characters that isn't speaker. if it is not blank, it will use this format instead of the default format. if `Group Other Bot Role` is `assistant`, it will also be applied to the speaker.",
groupOtherBotRole: "This defines a role that is used in group chat for characters that isn't speaker.", groupOtherBotRole: "This defines a role that is used in group chat for characters that isn't speaker.",
chatHTML: "A HTML that would be inserted as each chat.\n\nYou can use CBS and special tags.\n- `<risutext>`: a textbox that would be used to render text\n- `<risuicon>`: an icon for user or assistant\n- `<risubuttons>`: icon buttons for chat edit, translations and etc.\n- `<risugeninfo>`: generation information button."
}, },
setup: { setup: {
chooseProvider: "Choose AI Provider", chooseProvider: "Choose AI Provider",
@@ -749,4 +750,5 @@ export const languageEnglish = {
groupInnerFormat: "Non-Speaker Inner Format", groupInnerFormat: "Non-Speaker Inner Format",
groupOtherBotRole: "Non-Speaker Role in Group", groupOtherBotRole: "Non-Speaker Role in Group",
defineCustomGUI: "Define Custom GUI", defineCustomGUI: "Define Custom GUI",
chatHTML: "Chat HTML",
} }

View File

@@ -191,6 +191,19 @@
onDestroy(()=>{ onDestroy(()=>{
unsubscribers.forEach(u => u()) unsubscribers.forEach(u => u())
}) })
function RenderGUIHtml(html:string){
try {
const parser = new DOMParser()
const doc = parser.parseFromString(risuChatParser(html ?? ''), 'text/html')
console.log(doc.body)
return doc.body
} catch (error) {
const placeholder = document.createElement('div')
return placeholder
}
}
</script> </script>
@@ -227,7 +240,7 @@
{:else} {:else}
<!-- svelte-ignore a11y_click_events_have_key_events --> <!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<span class="text chat chattext prose minw-0" class:prose-invert={$ColorSchemeTypeStore} onclick={() => { <span class="text chat-width chattext prose minw-0" class:prose-invert={$ColorSchemeTypeStore} onclick={() => {
if(DBState.db.clickToEdit && idx > -1){ if(DBState.db.clickToEdit && idx > -1){
editMode = true editMode = true
} }
@@ -336,6 +349,148 @@
{/if} {/if}
{/snippet} {/snippet}
{#snippet renderGuiHtmlPart(dom:HTMLElement)}
{#if dom.tagName === 'IMG'}
<img class={dom.getAttribute('class') ?? ''} alt="" style={dom.getAttribute('style') ?? ''} />
{:else if dom.tagName === 'A'}
<a target="_blank" rel="noreferrer" href={
(dom.getAttribute('href') && dom.getAttribute('href').startsWith('https')) ? dom.getAttribute('href') : ''
} class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</a>
{:else if dom.tagName === 'SPAN'}
<span class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</span>
{:else if dom.tagName === 'DIV'}
<div class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</div>
{:else if dom.tagName === 'P'}
<p class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</p>
{:else if dom.tagName === 'H1'}
<h1 class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</h1>
{:else if dom.tagName === 'H2'}
<h2 class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</h2>
{:else if dom.tagName === 'H3'}
<h3 class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</h3>
{:else if dom.tagName === 'H4'}
<h4 class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</h4>
{:else if dom.tagName === 'H5'}
<h5 class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</h5>
{:else if dom.tagName === 'H6'}
<h6 class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</h6>
{:else if dom.tagName === 'UL'}
<ul class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</ul>
{:else if dom.tagName === 'OL'}
<ol class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</ol>
{:else if dom.tagName === 'LI'}
<li class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</li>
{:else if dom.tagName === 'TABLE'}
<table class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</table>
{:else if dom.tagName === 'TR'}
<tr class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</tr>
{:else if dom.tagName === 'TD'}
<td class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</td>
{:else if dom.tagName === 'TH'}
<th class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</th>
{:else if dom.tagName === 'HR'}
<hr class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''} />
{:else if dom.tagName === 'BR'}
<br class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''} />
{:else if dom.tagName === 'CODE'}
<code class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</code>
{:else if dom.tagName === 'PRE'}
<pre class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</pre>
{:else if dom.tagName === 'BLOCKQUOTE'}
<blockquote class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</blockquote>
{:else if dom.tagName === 'EM'}
<em class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</em>
{:else if dom.tagName === 'STRONG'}
<strong class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</strong>
{:else if dom.tagName === 'U'}
<u class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</u>
{:else if dom.tagName === 'DEL'}
<del class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</del>
{:else if dom.tagName === 'BUTTON'}
<button class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</button>
{:else if dom.tagName === 'STYLE'}
<!-- <div>
<style>
{dom.innerHTML}
</style>
</div> -->
{:else if dom.tagName === 'RISUTEXTBOX'}
{@render textBox()}
{:else if dom.tagName === 'RISUICON'}
{@render icon()}
{:else if dom.tagName === 'RISUBUTTONS'}
{@render icons()}
{:else if dom.tagName === 'RISUGENINFO'}
{@render genInfo()}
{:else if dom.tagName === 'STYLE'}
<svelte:element this={'style'}>
{dom.innerHTML}
</svelte:element>
{:else}
<div class={dom.getAttribute('class') ?? ''} style={dom.getAttribute('style') ?? ''}>
{@render renderChilds(dom)}
</div>
{/if}
{/snippet}
{#snippet renderChilds(dom:HTMLElement)}
{#each dom.children as node}
{@render renderGuiHtmlPart((node as HTMLElement))}
{/each}
{/snippet}
<div class="flex max-w-full justify-center risu-chat" style={isLastMemory ? `border-top:${DBState.db.memoryLimitThickness}px solid rgba(98, 114, 164, 0.7);` : ''}> <div class="flex max-w-full justify-center risu-chat" style={isLastMemory ? `border-top:${DBState.db.memoryLimitThickness}px solid rgba(98, 114, 164, 0.7);` : ''}>
<div class="text-textcolor mt-1 ml-4 mr-4 mb-1 p-2 bg-transparent flex-grow border-t-gray-900 border-opacity-30 border-transparent flexium items-start max-w-full" > <div class="text-textcolor mt-1 ml-4 mr-4 mb-1 p-2 bg-transparent flex-grow border-t-gray-900 border-opacity-30 border-transparent flexium items-start max-w-full" >
{#if DBState.db.theme === 'mobilechat' && !blankMessage} {#if DBState.db.theme === 'mobilechat' && !blankMessage}
@@ -390,12 +545,14 @@
{@render icons({applyTextColors: false})} {@render icons({applyTextColors: false})}
</div> </div>
</div> </div>
{:else if DBState.db.theme === 'customHTML' && !blankMessage}
{@render renderGuiHtmlPart(RenderGUIHtml(DBState.db.guiHTML))}
{:else} {:else}
{@render icon({rounded: DBState.db.roundIcons})} {@render icon({rounded: DBState.db.roundIcons})}
<span class="flex flex-col ml-4 w-full max-w-full min-w-0 text-black"> <span class="flex flex-col ml-4 w-full max-w-full min-w-0 text-black">
<div class="flexium items-center chat"> <div class="flexium items-center chat-width">
{#if DBState.db.characters[$selectedCharID]?.chaId === "§playground" && !blankMessage} {#if DBState.db.characters[$selectedCharID]?.chaId === "§playground" && !blankMessage}
<span class="chat text-xl border-darkborderc flex items-center"> <span class="chat-width text-xl border-darkborderc flex items-center">
<span>{name === 'assistant' ? 'Assistant' : 'User'}</span> <span>{name === 'assistant' ? 'Assistant' : 'User'}</span>
<button class="ml-2 text-textcolor2 hover:text-textcolor" onclick={() => { <button class="ml-2 text-textcolor2 hover:text-textcolor" onclick={() => {
DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].message[idx].role = DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].message[idx].role === 'char' ? 'user' : 'char' DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].message[idx].role = DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].message[idx].role === 'char' ? 'user' : 'char'
@@ -403,7 +560,7 @@
}}><ArrowLeftRightIcon size="18" /></button> }}><ArrowLeftRightIcon size="18" /></button>
</span> </span>
{:else if !blankMessage && !$HideIconStore} {:else if !blankMessage && !$HideIconStore}
<span class="chat text-xl unmargin text-textcolor">{name}</span> <span class="chat-width text-xl unmargin text-textcolor">{name}</span>
{/if} {/if}
{@render icons()} {@render icons()}
</div> </div>
@@ -412,21 +569,4 @@
</span> </span>
{/if} {/if}
</div> </div>
</div> </div>
<style>
.flexium{
display: flex;
flex-direction: row;
justify-content: flex-start;
}
.chat{
max-width: calc(100% - 0.5rem);
word-break: normal;
overflow-wrap: anywhere;
}
.translating{
color: rgba(16, 185, 129, 1);
}
</style>

View File

@@ -57,7 +57,7 @@
<OptionInput value="mobilechat" >Mobile Chat</OptionInput> <OptionInput value="mobilechat" >Mobile Chat</OptionInput>
<OptionInput value="cardboard" >CardBoard</OptionInput> <OptionInput value="cardboard" >CardBoard</OptionInput>
<!-- <OptionInput value="custom" >Custom GUI</OptionInput> --> <OptionInput value="customHTML" >Custom HTML</OptionInput>
</SelectInput> </SelectInput>
{#if DBState.db.theme === "custom"} {#if DBState.db.theme === "custom"}
@@ -67,6 +67,12 @@
{/if} {/if}
{#if DBState.db.theme === 'customHTML'}
<span class="text-textcolor mt-4">{language.chatHTML} <Help key="chatHTML"/></span>
<TextAreaInput bind:value={DBState.db.guiHTML} />
{/if}
{#if DBState.db.theme === "waifu"} {#if DBState.db.theme === "waifu"}
<span class="text-textcolor mt-4">{language.waifuWidth}</span> <span class="text-textcolor mt-4">{language.waifuWidth}</span>
<SliderInput min={50} max={200} bind:value={DBState.db.waifuWidth} /> <SliderInput min={50} max={200} bind:value={DBState.db.waifuWidth} />

View File

@@ -35,7 +35,7 @@
oninput={handleInput} oninput={handleInput}
use:longpress={handleLongPress} use:longpress={handleLongPress}
bind:value={value} bind:value={value}
class="rounded-md p-2 text-textcolor bg-transparent resize-none overflow-y-hidden border border-darkborderc" class="rounded-md p-2 text-textcolor bg-transparent resize-none overflow-y-hidden border border-darkborderc w-full"
style:font-size="{0.875 * (DBState.db.zoomsize / 100)}rem" style:font-size="{0.875 * (DBState.db.zoomsize / 100)}rem"
style:line-height="{(DBState.db.lineHeight ?? 1.25) * (DBState.db.zoomsize / 100)}rem" style:line-height="{(DBState.db.lineHeight ?? 1.25) * (DBState.db.zoomsize / 100)}rem"
></textarea> ></textarea>

View File

@@ -254,4 +254,15 @@ html, body{
.z-100{ .z-100{
z-index: 100; z-index: 100;
}
.flexium{
display: flex;
flex-direction: row;
justify-content: flex-start;
}
.chat-width{
max-width: calc(100% - 0.5rem);
word-break: normal;
overflow-wrap: anywhere;
} }

View File

@@ -813,6 +813,7 @@ export interface Database{
groupTemplate?:string groupTemplate?:string
groupOtherBotRole?:string groupOtherBotRole?:string
customGUI:string customGUI:string
guiHTML:string
} }
export interface customscript{ export interface customscript{