Add autocomplete
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<div
|
<div
|
||||||
class={"border border-darkborderc relative z-20 n-scroll focus-within:border-borderc rounded-md shadow-sm text-textcolor bg-transparent focus-within:ring-borderc focus-within:ring-2 focus-within:outline-none transition-colors duration-200" + ((className) ? (' ' + className) : '')}
|
class={"border border-darkborderc relative n-scroll focus-within:border-borderc rounded-md shadow-sm text-textcolor bg-transparent focus-within:ring-borderc focus-within:ring-2 focus-within:outline-none transition-colors duration-200 z-20 focus-within:z-40" + ((className) ? (' ' + className) : '')}
|
||||||
class:text-sm={size === 'sm' || (size === 'default' && $textAreaTextSize === 1)}
|
class:text-sm={size === 'sm' || (size === 'default' && $textAreaTextSize === 1)}
|
||||||
class:text-md={size === 'md' || (size === 'default' && $textAreaTextSize === 2)}
|
class:text-md={size === 'md' || (size === 'default' && $textAreaTextSize === 2)}
|
||||||
class:text-lg={size === 'lg' || (size === 'default' && $textAreaTextSize === 3)}
|
class:text-lg={size === 'lg' || (size === 'default' && $textAreaTextSize === 3)}
|
||||||
@@ -34,6 +34,9 @@
|
|||||||
class:mt-4={margin === 'top'}
|
class:mt-4={margin === 'top'}
|
||||||
class:mt-2={margin === 'both'}
|
class:mt-2={margin === 'both'}
|
||||||
bind:this={highlightDom}
|
bind:this={highlightDom}
|
||||||
|
on:focusout={() => {
|
||||||
|
hideAutoComplete()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{#if !highlight || !CSS.highlights || isFirefox}
|
{#if !highlight || !CSS.highlights || isFirefox}
|
||||||
<textarea
|
<textarea
|
||||||
@@ -70,16 +73,27 @@
|
|||||||
contenteditable="plaintext-only"
|
contenteditable="plaintext-only"
|
||||||
bind:innerText={value}
|
bind:innerText={value}
|
||||||
on:keydown={(e) => {
|
on:keydown={(e) => {
|
||||||
|
handleKeyDown(e)
|
||||||
onInput()
|
onInput()
|
||||||
}}
|
}}
|
||||||
|
on:input={(e) => {
|
||||||
|
autoComplete()
|
||||||
|
}}
|
||||||
translate="no"
|
translate="no"
|
||||||
|
|
||||||
>{value ?? ''}</div>
|
>{value ?? ''}</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
<div class="hidden absolute z-100 bg-bgcolor border border-darkborderc p-2 flex-col" bind:this={autoCompleteDom}>
|
||||||
|
{#each autocompleteContents as content, i}
|
||||||
|
<button class="w-full text-left py-1 px-2 bg-bgcolor" class:text-blue-500={selectingAutoComplete === i} on:click={() => {
|
||||||
|
insertAutoComplete(content)
|
||||||
|
}}>{content}</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { textAreaSize, textAreaTextSize } from 'src/ts/gui/guisize'
|
import { textAreaSize, textAreaTextSize } from 'src/ts/gui/guisize'
|
||||||
import { highlighter, getNewHighlightId, removeHighlight } from 'src/ts/gui/highlight'
|
import { highlighter, getNewHighlightId, removeHighlight, AllCBS } from 'src/ts/gui/highlight'
|
||||||
|
import { isMobile } from 'src/ts/storage/globalApi';
|
||||||
import { isFirefox, sleep } from 'src/ts/util';
|
import { isFirefox, sleep } from 'src/ts/util';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
export let size: 'xs'|'sm'|'md'|'lg'|'xl'|'default' = 'default'
|
export let size: 'xs'|'sm'|'md'|'lg'|'xl'|'default' = 'default'
|
||||||
@@ -95,10 +109,86 @@
|
|||||||
export let className = ''
|
export let className = ''
|
||||||
export let optimaizedInput = true
|
export let optimaizedInput = true
|
||||||
export let highlight = false
|
export let highlight = false
|
||||||
|
let selectingAutoComplete = -1
|
||||||
let highlightId = highlight ? getNewHighlightId() : 0
|
let highlightId = highlight ? getNewHighlightId() : 0
|
||||||
let inpa = 0
|
let inpa = 0
|
||||||
let highlightDom: HTMLDivElement
|
let highlightDom: HTMLDivElement
|
||||||
let optiValue = value
|
let optiValue = value
|
||||||
|
let autoCompleteDom: HTMLDivElement
|
||||||
|
let autocompleteContents:string[] = []
|
||||||
|
|
||||||
|
const autoComplete = () => {
|
||||||
|
if(isMobile){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//autocomplete
|
||||||
|
selectingAutoComplete = -1
|
||||||
|
const sel = window.getSelection()
|
||||||
|
if(!sel){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const range = sel.getRangeAt(0)
|
||||||
|
|
||||||
|
if(range){
|
||||||
|
const qValue = (range.startContainer).textContent
|
||||||
|
const splited = qValue.substring(0, range.startOffset).split('{{')
|
||||||
|
if(splited.length === 1){
|
||||||
|
hideAutoComplete()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const qText = splited.pop()
|
||||||
|
let filtered = AllCBS.filter((cb) => cb.startsWith(qText))
|
||||||
|
if(filtered.length === 0){
|
||||||
|
hideAutoComplete()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filtered = filtered.slice(0, 10)
|
||||||
|
autocompleteContents = filtered
|
||||||
|
}
|
||||||
|
|
||||||
|
const hlRect = highlightDom.getBoundingClientRect()
|
||||||
|
const rect = range.getBoundingClientRect()
|
||||||
|
if(rect.top === 0 && rect.left === 0){
|
||||||
|
hideAutoComplete()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const top = rect.top - hlRect.top + 15
|
||||||
|
const left = rect.left - hlRect.left
|
||||||
|
autoCompleteDom.style.top = top + 'px'
|
||||||
|
autoCompleteDom.style.left = left + 'px'
|
||||||
|
autoCompleteDom.style.display = 'flex'
|
||||||
|
}
|
||||||
|
|
||||||
|
const insertAutoComplete = (insertContent:string) => {
|
||||||
|
const sel = window.getSelection()
|
||||||
|
if(sel){
|
||||||
|
const range = sel.getRangeAt(0)
|
||||||
|
let content = (range.startContainer).textContent
|
||||||
|
let contentStart = content.substring(0, range.startOffset)
|
||||||
|
let contentEnd = content.substring(range.startOffset)
|
||||||
|
contentStart = contentStart.substring(0, contentStart.lastIndexOf('{{'))
|
||||||
|
|
||||||
|
if(insertContent.endsWith(':')){
|
||||||
|
insertContent = `{{${insertContent}:`
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
insertContent = `{{${insertContent}}}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const cons = contentStart + insertContent + contentEnd
|
||||||
|
range.startContainer.textContent = cons
|
||||||
|
|
||||||
|
sel.collapse(range.startContainer, contentStart.length + insertContent.length)
|
||||||
|
hideAutoComplete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hideAutoComplete = () => {
|
||||||
|
autoCompleteDom.style.display = 'none'
|
||||||
|
selectingAutoComplete = -1
|
||||||
|
autocompleteContents = []
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
highlighter(highlightDom, highlightId)
|
highlighter(highlightDom, highlightId)
|
||||||
@@ -113,6 +203,31 @@
|
|||||||
highlighter(highlightDom, highlightId)
|
highlighter(highlightDom, highlightId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleKeyDown = (e:KeyboardEvent) => {
|
||||||
|
if(autocompleteContents.length >= 1){
|
||||||
|
switch(e.key){
|
||||||
|
case 'ArrowDown':
|
||||||
|
selectingAutoComplete = Math.min(selectingAutoComplete + 1, autocompleteContents.length - 1)
|
||||||
|
e.preventDefault()
|
||||||
|
break
|
||||||
|
case 'ArrowUp':
|
||||||
|
selectingAutoComplete = Math.max(selectingAutoComplete - 1, 0)
|
||||||
|
e.preventDefault()
|
||||||
|
break
|
||||||
|
case 'Enter':
|
||||||
|
case 'Tab':
|
||||||
|
e.preventDefault()
|
||||||
|
if(selectingAutoComplete !== -1){
|
||||||
|
insertAutoComplete(autocompleteContents[selectingAutoComplete])
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'Escape':
|
||||||
|
hideAutoComplete()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$: optiValue = value
|
$: optiValue = value
|
||||||
$: highlightChange(value, highlightId)
|
$: highlightChange(value, highlightId)
|
||||||
|
|
||||||
|
|||||||
@@ -251,3 +251,7 @@ html, body{
|
|||||||
.x-risu-risu-inlay-image > img{
|
.x-risu-risu-inlay-image > img{
|
||||||
@apply rounded-lg focus:outline-none max-w-80 w-full
|
@apply rounded-lg focus:outline-none max-w-80 w-full
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.z-100{
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
@@ -80,8 +80,8 @@ export const removeHighlight = (id:number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const normalCBS = [
|
const normalCBS = [
|
||||||
'previous_char_chat', 'lastcharmessage', 'previous_user_chat', 'lastusermessage', 'char', 'bot',
|
'char', 'user', 'char_persona', 'description', 'char_desc', 'example_dialogue', 'previous_char_chat',
|
||||||
'user', 'char_persona', 'description', 'char_desc', 'example_dialogue',
|
'lastcharmessage', 'previous_user_chat', 'lastusermessage',
|
||||||
'example_message', 'persona', 'user_persona', 'lorebook', 'world_info', 'history', 'messages',
|
'example_message', 'persona', 'user_persona', 'lorebook', 'world_info', 'history', 'messages',
|
||||||
'chat_index', 'first_msg_index', 'blank', 'none', 'message_time', 'message_date', 'time',
|
'chat_index', 'first_msg_index', 'blank', 'none', 'message_time', 'message_date', 'time',
|
||||||
'date', 'isotime', 'isodate', 'message_idle_duration', 'idle_duration', 'br', 'newline',
|
'date', 'isotime', 'isodate', 'message_idle_duration', 'idle_duration', 'br', 'newline',
|
||||||
@@ -105,8 +105,12 @@ const displayRelatedCBS = [
|
|||||||
'raw', 'img', 'video', 'audio', 'bg', 'emotion', 'asset', 'video-img', 'comment', 'image'
|
'raw', 'img', 'video', 'audio', 'bg', 'emotion', 'asset', 'video-img', 'comment', 'image'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const nestedCBS = [
|
||||||
|
'#if', '#if_pure ', '#pure ', '#each ', '#func', '#pure_display'
|
||||||
|
]
|
||||||
|
|
||||||
const specialCBS = [
|
const specialCBS = [
|
||||||
'#if', '#if_pure ', '#pure ', '#each ', 'random:', 'pick:', 'roll:', 'datetimeformat:', '? ', 'hidden_key: ', 'reverse: ', '#func', '#pure_display'
|
'random:', 'pick:', 'roll:', 'datetimeformat:', '? ', 'hidden_key: ', 'reverse: ', ...nestedCBS
|
||||||
]
|
]
|
||||||
|
|
||||||
const deprecatedCBS = [
|
const deprecatedCBS = [
|
||||||
@@ -126,6 +130,11 @@ const decorators = [
|
|||||||
const deprecatedDecorators = [
|
const deprecatedDecorators = [
|
||||||
'end', 'assistant', 'user', 'system'
|
'end', 'assistant', 'user', 'system'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const AllCBS = [...normalCBS, ...(normalCBSwithParams.map((v) => {
|
||||||
|
return v + ':'
|
||||||
|
})), ...displayRelatedCBS, ...nestedCBS]
|
||||||
|
|
||||||
const highlighterSyntax = [
|
const highlighterSyntax = [
|
||||||
{
|
{
|
||||||
regex: /<(char|user|bot)>/gi,
|
regex: /<(char|user|bot)>/gi,
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export const isTauri = !!window.__TAURI__
|
|||||||
export const isNodeServer = !!globalThis.__NODE__
|
export const isNodeServer = !!globalThis.__NODE__
|
||||||
export const forageStorage = new AutoStorage()
|
export const forageStorage = new AutoStorage()
|
||||||
export const googleBuild = false
|
export const googleBuild = false
|
||||||
|
export const isMobile = navigator.userAgent.match(/(iPad)|(iPhone)|(iPod)|(android)|(webOS)/i)
|
||||||
|
|
||||||
interface fetchLog{
|
interface fetchLog{
|
||||||
body:string
|
body:string
|
||||||
|
|||||||
Reference in New Issue
Block a user