Add autocomplete

This commit is contained in:
kwaroran
2024-09-23 23:53:03 +09:00
parent cbba839cea
commit e676623a4e
4 changed files with 135 additions and 6 deletions

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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