Add autocomplete
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<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-md={size === 'md' || (size === 'default' && $textAreaTextSize === 2)}
|
||||
class:text-lg={size === 'lg' || (size === 'default' && $textAreaTextSize === 3)}
|
||||
@@ -34,6 +34,9 @@
|
||||
class:mt-4={margin === 'top'}
|
||||
class:mt-2={margin === 'both'}
|
||||
bind:this={highlightDom}
|
||||
on:focusout={() => {
|
||||
hideAutoComplete()
|
||||
}}
|
||||
>
|
||||
{#if !highlight || !CSS.highlights || isFirefox}
|
||||
<textarea
|
||||
@@ -70,16 +73,27 @@
|
||||
contenteditable="plaintext-only"
|
||||
bind:innerText={value}
|
||||
on:keydown={(e) => {
|
||||
handleKeyDown(e)
|
||||
onInput()
|
||||
}}
|
||||
on:input={(e) => {
|
||||
autoComplete()
|
||||
}}
|
||||
translate="no"
|
||||
|
||||
>{value ?? ''}</div>
|
||||
{/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>
|
||||
<script lang="ts">
|
||||
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 { onDestroy, onMount } from 'svelte';
|
||||
export let size: 'xs'|'sm'|'md'|'lg'|'xl'|'default' = 'default'
|
||||
@@ -95,10 +109,86 @@
|
||||
export let className = ''
|
||||
export let optimaizedInput = true
|
||||
export let highlight = false
|
||||
let selectingAutoComplete = -1
|
||||
let highlightId = highlight ? getNewHighlightId() : 0
|
||||
let inpa = 0
|
||||
let highlightDom: HTMLDivElement
|
||||
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(() => {
|
||||
highlighter(highlightDom, highlightId)
|
||||
@@ -112,6 +202,31 @@
|
||||
await sleep(1)
|
||||
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
|
||||
$: highlightChange(value, highlightId)
|
||||
|
||||
@@ -250,4 +250,8 @@ html, body{
|
||||
|
||||
.x-risu-risu-inlay-image > img{
|
||||
@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 = [
|
||||
'previous_char_chat', 'lastcharmessage', 'previous_user_chat', 'lastusermessage', 'char', 'bot',
|
||||
'user', 'char_persona', 'description', 'char_desc', 'example_dialogue',
|
||||
'char', 'user', 'char_persona', 'description', 'char_desc', 'example_dialogue', 'previous_char_chat',
|
||||
'lastcharmessage', 'previous_user_chat', 'lastusermessage',
|
||||
'example_message', 'persona', 'user_persona', 'lorebook', 'world_info', 'history', 'messages',
|
||||
'chat_index', 'first_msg_index', 'blank', 'none', 'message_time', 'message_date', 'time',
|
||||
'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'
|
||||
];
|
||||
|
||||
const nestedCBS = [
|
||||
'#if', '#if_pure ', '#pure ', '#each ', '#func', '#pure_display'
|
||||
]
|
||||
|
||||
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 = [
|
||||
@@ -126,6 +130,11 @@ const decorators = [
|
||||
const deprecatedDecorators = [
|
||||
'end', 'assistant', 'user', 'system'
|
||||
]
|
||||
|
||||
export const AllCBS = [...normalCBS, ...(normalCBSwithParams.map((v) => {
|
||||
return v + ':'
|
||||
})), ...displayRelatedCBS, ...nestedCBS]
|
||||
|
||||
const highlighterSyntax = [
|
||||
{
|
||||
regex: /<(char|user|bot)>/gi,
|
||||
|
||||
@@ -43,6 +43,7 @@ export const isTauri = !!window.__TAURI__
|
||||
export const isNodeServer = !!globalThis.__NODE__
|
||||
export const forageStorage = new AutoStorage()
|
||||
export const googleBuild = false
|
||||
export const isMobile = navigator.userAgent.match(/(iPad)|(iPhone)|(iPod)|(android)|(webOS)/i)
|
||||
|
||||
interface fetchLog{
|
||||
body:string
|
||||
|
||||
Reference in New Issue
Block a user