diff --git a/src/lang/en.ts b/src/lang/en.ts index 88574fbd..17431ea9 100644 --- a/src/lang/en.ts +++ b/src/lang/en.ts @@ -375,6 +375,12 @@ export const languageEnglish = { v2SetRequestStateRoleDesc: "Set Request Data Role at {{index}} to {{value}}", v2GetRequestStateLength: "Get Request Data Length", v2GetRequestStateLengthDesc: "Get Request Data Length => {{outputVar}}", + v2IfAdvanced: "If (Advanced)", + v2IfAdvancedDesc: "If {{source}} {{condition}} {{target}}", + v2QuickSearchChat: "Quick Search Chat", + v2QuickSearchChatDesc: "Search {{value}} {{condition}} at depth {{depth}} => {{outputVar}}", + v2StopPromptSending: "Stop Prompt Sending", + v2StopPromptSendingDesc: "Stop Prompt Sending", }, confirm: "Confirm", @@ -1046,4 +1052,7 @@ export const languageEnglish = { bulkEnabling: "Lorebook Bulk Enabling", showDeprecatedTriggerV1: "Show Deprecated Trigger V1", triggerV1Warning: "Trigger V1 is deprecated. it might be removed in the future.", + copy: "Copy", + paste: "Paste", + depth: "Depth", } diff --git a/src/lang/ko.ts b/src/lang/ko.ts index f2527ff1..9e7b4bc8 100644 --- a/src/lang/ko.ts +++ b/src/lang/ko.ts @@ -234,7 +234,7 @@ export const languageKorean = { "v2ShowAlert": "알림 표시", "v2ShowAlertDesc": "{{value}}로 알림 표시", "v2ExtractRegex": "정규식 추출", - "v2ExtractRegexDesc": "{{value}}에서 정규식 {{regexType}} {{regex}} 및 플래그 {{flagsType}} {{flags}}를 사용하여 텍스트 추출, 결과를 {{resultType}} {{result}}로 저장 => {{outputVar}}", + "v2ExtractRegexDesc": "{{value}}에서 정규식 {{regex}} 및 플래그 {{flags}}를 사용하여 텍스트 추출, 결과를 {{result}}로 저장 => {{outputVar}}", "v2GetLastMessage": "마지막 메시지 가져오기", "v2GetLastMessageDesc": "마지막 메시지 가져오기 => {{outputVar}}", "v2GetMessageAtIndex": "인덱스에서 메시지 가져오기", @@ -327,6 +327,12 @@ export const languageKorean = { "v2SetRequestStateRoleDesc": "{{index}}에 있는 리퀘스트 데이터 역할을 {{value}}로 설정", "v2GetRequestStateLength": "리퀘스트 데이터 길이 가져오기", "v2GetRequestStateLengthDesc": "리퀘스트 데이터 길이 가져오기 => {{outputVar}}", + "v2IfAdvanced": "만약 ~이라면 (고급)", + "v2IfAdvancedDesc": "만약 {{source}} {{condition}} {{target}} 이라면", + "v2QuickSearchChat": "빠른 채팅 검색", + "v2QuickSearchChatDesc": "{{value}}를 {{depth}} 깊이에서 {{condition}} 방식으로 검색 => {{outputVar}}", + "v2StopPromptSending": "프롬프트 전송 중단", + "v2StopPromptSendingDesc": "프롬프트 전송 중단", }, "confirm": "확인", "goback": "뒤로", @@ -968,4 +974,7 @@ export const languageKorean = { "bulkEnabling": "한번에 로어북 활성화 버튼", "showDeprecatedTriggerV1": "비권장 트리거 V1 보이기", "triggerV1Warning": "트리거 V1은 비권장입니다. 추후에 제거될 수 있습니다.", + "copy": "복사", + "paste": "붙여넣기", + "depth": "깊이", } diff --git a/src/lib/SideBars/Scripts/TriggerList2.svelte b/src/lib/SideBars/Scripts/TriggerList2.svelte index 86a86ebf..de435b64 100644 --- a/src/lib/SideBars/Scripts/TriggerList2.svelte +++ b/src/lib/SideBars/Scripts/TriggerList2.svelte @@ -4,11 +4,10 @@ import Button from "src/lib/UI/GUI/Button.svelte"; import CheckInput from "src/lib/UI/GUI/CheckInput.svelte"; import OptionInput from "src/lib/UI/GUI/OptionInput.svelte"; - import Portal from "src/lib/UI/GUI/Portal.svelte"; + import Portal from "src/lib/UI/GUI/Portal.svelte"; import SelectInput from "src/lib/UI/GUI/SelectInput.svelte"; import TextInput from "src/lib/UI/GUI/TextInput.svelte"; - import { type triggerEffectV2, type triggerEffect, type triggerscript, displayAllowList, requestAllowList } from "src/ts/process/triggers"; - import { sleep } from "src/ts/util"; + import { type triggerEffectV2, type triggerEffect, type triggerscript, displayAllowList, requestAllowList, type triggerV2IfAdvanced } from "src/ts/process/triggers"; import { onDestroy, onMount } from "svelte"; interface Props { @@ -29,15 +28,13 @@ //Control 'v2SetVar', 'v2If', + 'v2IfAdvanced', 'v2LoopNTimes', 'v2Loop', 'v2BreakLoop', 'v2RunTrigger', 'v2ConsoleLog', 'v2StopTrigger', - 'v2Random', - 'v2UpdateGUI', - 'v2Wait', //Chat 'v2CutChat', @@ -51,6 +48,7 @@ 'v2GetMessageAtIndex', 'v2GetMessageCount', 'v2GetFirstMessage', + 'v2QuickSearchChat', //Low Level 'v2SendAIprompt', @@ -96,7 +94,13 @@ 'v2SpliceArrayVar', 'v2SliceArrayVar', 'v2GetIndexOfValueInArrayVar', - 'v2RemoveIndexFromArrayVar' + 'v2RemoveIndexFromArrayVar', + + //Others + 'v2Random', + 'v2UpdateGUI', + 'v2Wait', + "v2StopPromptSending", ] @@ -125,7 +129,19 @@ let menuMode = $state(0) let editTrigger:triggerEffectV2 = $state(null as triggerEffectV2) let addElse = $state(false) - + let selectMode = $state(0) //0 = trigger 1 = effect + let contextMenu = $state(false) + let contextMenuLoc = $state({x: 0, y: 0}) + + type VirtualClipboard = { + type: 'trigger', + value: triggerscript[] + }|{ + type: 'effect', + value: triggerEffect[] + } + let clipboard:VirtualClipboard = $state(null) + $effect(() => { if(menuMode === 0){ @@ -175,6 +191,17 @@ source: '' } break; + case 'v2IfAdvanced': + editTrigger = { + type: 'v2IfAdvanced', + indent: 0, + condition: '=', + targetType: 'value', + target: '', + sourceType: 'value', + source: '', + } + break; case 'v2Else': editTrigger = { type: 'v2Else', @@ -733,13 +760,33 @@ } break; } + case 'v2StopPromptSending':{ + editTrigger = { + type: 'v2StopPromptSending', + indent: 0 + } + break; + } + case 'v2QuickSearchChat':{ + editTrigger = { + type: 'v2QuickSearchChat', + value: '', + valueType: 'value', + indent: 0, + condition: 'loose', + depth: '3', + depthType: 'value', + outputVar: '' + } + break; + } } } const deleteEffect = () => { const type = value[selectedIndex].effect[selectedEffectIndex] value[selectedIndex].effect.splice(selectedEffectIndex, 1) - if(type.type === 'v2If' || type.type === 'v2Loop' || type.type === 'v2Else' || type.type === 'v2LoopNTimes'){ + if(type.type === 'v2If' || type.type === 'v2IfAdvanced' || type.type === 'v2Loop' || type.type === 'v2Else' || type.type === 'v2LoopNTimes'){ let pointer = selectedEffectIndex let indent = (type as triggerEffectV2).indent while(pointer < value[selectedIndex].effect.length){ @@ -763,46 +810,121 @@ if(selectedEffectIndex < 0){ selectedEffectIndex = 0 } -} + } + + const copyEffect = () => { + const type = value[selectedIndex].effect[selectedEffectIndex] + if(type.type === 'v2If' || type.type === 'v2IfAdvanced' || type.type === 'v2Loop' || type.type === 'v2Else' || type.type === 'v2LoopNTimes'){ + return + } + clipboard = { + type: 'effect', + value: safeStructuredClone([type]) + } + } + + const pasteEffect = async () => { + if(clipboard?.type !== 'effect'){ + return + } + + for(const effect of clipboard.value){ + value[selectedIndex].effect.splice(selectedEffectIndex, 0, effect) + selectedEffectIndex += 1 + } + } + + const copyTrigger = () => { + clipboard = { + type: 'trigger', + value: safeStructuredClone([value[selectedIndex]]) + } + } + + const pasteTrigger = async () => { + if(clipboard?.type !== 'trigger'){ + return + } + + for(const trigger of clipboard.value){ + value.splice(selectedIndex, 0, trigger) + selectedIndex += 1 + } + } + + const deleteTrigger = () => { + if(value.length <= 2){ + return + } + value.splice(selectedIndex, 1) + selectedIndex -= 1 + if(selectedIndex < 1){ + selectedIndex = 1 + } + } const handleKeydown = (e:KeyboardEvent) => { console.log(e.key) if(e.key === 'Escape'){ - close(); + if(menuMode === 0){ + close() + } + else{ + menuMode = 0 + } } - if(selectedIndex > 0 && selectedEffectIndex !== -1 && menuMode === 0){ + if(selectedIndex > 0 && selectedEffectIndex !== -1 && menuMode === 0 && selectMode === 1){ if(e.key === 'ArrowUp'){ if(selectedEffectIndex > 0){ selectedEffectIndex -= 1 + + if(e.altKey){ + const before = value[selectedIndex].effect[selectedEffectIndex] as triggerEffectV2 + const after = value[selectedIndex].effect[selectedEffectIndex + 1] as triggerEffectV2 + if( + before.type !== 'v2EndIndent' && after.type !== 'v2EndIndent' + && before.type !== 'v2If' && after.type !== 'v2If' + && before.type !== 'v2IfAdvanced' && after.type !== 'v2IfAdvanced' + && before.type !== 'v2Loop' && after.type !== 'v2Loop' + && before.type !== 'v2LoopNTimes' && after.type !== 'v2LoopNTimes' + && before.indent === after.indent + ){ + value[selectedIndex].effect[selectedEffectIndex] = after + value[selectedIndex].effect[selectedEffectIndex + 1] = before + } + } } e.preventDefault() } if(e.key === 'ArrowDown'){ if(selectedEffectIndex < value[selectedIndex].effect.length - 1){ selectedEffectIndex += 1 + + if(e.altKey){ + const before = value[selectedIndex].effect[selectedEffectIndex] as triggerEffectV2 + const after = value[selectedIndex].effect[selectedEffectIndex - 1] as triggerEffectV2 + if( + before.type !== 'v2EndIndent' && after.type !== 'v2EndIndent' + && before.type !== 'v2If' && after.type !== 'v2If' + && before.type !== 'v2IfAdvanced' && after.type !== 'v2IfAdvanced' + && before.type !== 'v2Loop' && after.type !== 'v2Loop' + && before.type !== 'v2LoopNTimes' && after.type !== 'v2LoopNTimes' + && before.indent === after.indent + ){ + value[selectedIndex].effect[selectedEffectIndex] = after + value[selectedIndex].effect[selectedEffectIndex - 1] = before + } + } } e.preventDefault() } if(e.key === 'c' && e.ctrlKey){ - const type = value[selectedIndex].effect[selectedEffectIndex] - if(type.type === 'v2If' || type.type === 'v2Loop' || type.type === 'v2Else' || type.type === 'v2LoopNTimes'){ - return - } - - //copy - navigator.clipboard.writeText(JSON.stringify(value[selectedIndex].effect[selectedEffectIndex])) + copyEffect() e.preventDefault() } if(e.key === 'v' && e.ctrlKey){ //paste - navigator.clipboard.readText().then((text) => { - try { - value[selectedIndex].effect.splice(selectedEffectIndex, 0, JSON.parse(text)) - - } catch (error) { - console.error(error) - } - }) + pasteEffect() e.preventDefault() } if(e.key === 'Delete'){ @@ -810,6 +932,43 @@ e.preventDefault() } } + if(selectedIndex > 0 && menuMode === 0 && selectMode === 0){ + if(e.key === 'ArrowUp'){ + if(selectedIndex > 1){ + selectedIndex -= 1 + + if(e.altKey){ + const before = value[selectedIndex] + const after = value[selectedIndex + 1] + value[selectedIndex] = after + value[selectedIndex + 1] = before + } + } + e.preventDefault() + } + if(e.key === 'ArrowDown'){ + if(selectedIndex < value.length - 1){ + selectedIndex += 1 + + if(e.altKey){ + const before = value[selectedIndex] + const after = value[selectedIndex - 1] + value[selectedIndex] = after + value[selectedIndex - 1] = before + } + } + e.preventDefault() + } + if(e.key === 'c' && e.ctrlKey){ + copyTrigger() + e.preventDefault() + } + if(e.key === 'v' && e.ctrlKey){ + pasteTrigger() + e.preventDefault() + } + //Delete is forrbidden due to the fact that misclicks can cause huge data loss + } } const formatEffectDisplay = (effect:triggerEffect) => { @@ -862,16 +1021,65 @@ - { - close() -}} /> + {#if selectedIndex > 0} -
-
e.stopPropagation()} class:w-7xl={menuMode === 0} class:w-3xl={menuMode !== 0} class:h-full={menuMode!==2}> +
{ + e.stopPropagation() + contextMenu = false + }}> + {#if contextMenu} +
+ {#if selectedEffectIndex !== -1 && value[selectedIndex].effect[selectedEffectIndex].type !== 'v2EndIndent' && selectMode === 1} + + {/if} + + {#if (selectedEffectIndex !== -1 && value[selectedIndex].effect[selectedEffectIndex].type !== 'v2EndIndent') || selectMode === 0} + + {/if} + + + + {#if (selectedEffectIndex !== -1 && value[selectedIndex].effect[selectedEffectIndex].type !== 'v2EndIndent') || selectMode === 0} + + {/if} +
+ {/if} +
{#if menuMode === 0}
@@ -882,7 +1090,17 @@ @@ -906,7 +1124,10 @@
-
+
{ + selectMode = 1 + selectedEffectIndex = -1 + }}>
{language.name} { @@ -938,38 +1159,34 @@
{#each value[selectedIndex].effect as effect, i} - {#if effect.type === 'v2EndIndent'} - - {:else} - - {/if} + {/if} + {/each} @@ -1019,27 +1243,56 @@ {language.var} - {:else if editTrigger.type === 'v2If'} + {:else if editTrigger.type === 'v2If' || editTrigger.type === 'v2IfAdvanced'} {language.varName} + {#if editTrigger.type === 'v2IfAdvanced'} + + {language.value} + {language.var} + + {/if} {language.condition} - + { + if(e.currentTarget.value === '≡'){ + const trg = editTrigger as triggerV2IfAdvanced + trg.condition = '≡' + trg.target = 'true' + trg.targetType = 'value' + } + }}> = {">"} {"<"} {"≥"} {"≤"} + {#if editTrigger.type === 'v2IfAdvanced'} + + + + + + + {/if} {language.value} - - {language.value} - {language.var} - - + {#if editTrigger.condition === '≡'} + + true + false + null + + {:else} + + {language.value} + {language.var} + + + {/if} {:else if editTrigger.type === 'v2RunTrigger'} @@ -1633,6 +1886,31 @@ {:else if editTrigger.type === 'v2GetRequestStateLength'} {language.outputVar} + {:else if editTrigger.type === 'v2QuickSearchChat'} + {language.value} + + {language.value} + {language.var} + + + + {language.condition} + + loose + strict + regex + + + {language.depth} + + {language.value} + {language.var} + + + + {language.outputVar} + + {:else} {language.noConfig} {/if} @@ -1641,7 +1919,7 @@ if(selectedEffectIndex === -1){ value[selectedIndex].effect.push(editTrigger) - if(editTrigger.type === 'v2If' || editTrigger.type === 'v2Loop' || editTrigger.type === 'v2Else' || editTrigger.type === 'v2LoopNTimes'){ + if(editTrigger.type === 'v2If' || editTrigger.type === 'v2IfAdvanced' || editTrigger.type === 'v2Loop' || editTrigger.type === 'v2Else' || editTrigger.type === 'v2LoopNTimes'){ value[selectedIndex].effect.push({ type: 'v2EndIndent', indent: editTrigger.indent + 1, @@ -1662,7 +1940,7 @@ } else if(menuMode === 2){ editTrigger.indent = (value[selectedIndex].effect[selectedEffectIndex] as triggerEffectV2).indent - if(editTrigger.type === 'v2If' || editTrigger.type === 'v2Loop' || editTrigger.type === 'v2LoopNTimes' || editTrigger.type === 'v2Else'){ + if(editTrigger.type === 'v2If' || editTrigger.type === 'v2IfAdvanced' || editTrigger.type === 'v2Loop' || editTrigger.type === 'v2LoopNTimes' || editTrigger.type === 'v2Else'){ if(addElse){ value[selectedIndex].effect.splice(selectedEffectIndex, 0, { type: 'v2Else', diff --git a/src/ts/process/triggers.ts b/src/ts/process/triggers.ts index fff25062..14f314b5 100644 --- a/src/ts/process/triggers.ts +++ b/src/ts/process/triggers.ts @@ -38,7 +38,8 @@ export type triggerEffectV2 = triggerV2Header|triggerV2IfVar|triggerV2Else|tri triggerV2PushArrayVar|triggerV2PopArrayVar|triggerV2ShiftArrayVar|triggerV2UnshiftArrayVar|triggerV2SpliceArrayVar|triggerV2GetFirstMessage| triggerV2SliceArrayVar|triggerV2GetIndexOfValueInArrayVar|triggerV2RemoveIndexFromArrayVar|triggerV2ConcatString|triggerV2GetLastUserMessage| triggerV2GetLastCharMessage|triggerV2GetAlertInput|triggerV2GetDisplayState|triggerV2SetDisplayState|triggerV2UpdateGUI|triggerV2Wait| - triggerV2GetRequestState|triggerV2SetRequestState|triggerV2GetRequestStateRole|triggerV2SetRequestStateRole|triggerV2GetReuqestStateLength + triggerV2GetRequestState|triggerV2SetRequestState|triggerV2GetRequestStateRole|triggerV2SetRequestStateRole|triggerV2GetReuqestStateLength|triggerV2IfAdvanced| + triggerV2QuickSearchChat|triggerV2StopPromptSending export type triggerConditionsVar = { type:'var'|'value' @@ -173,7 +174,7 @@ export type triggerV2Header = { export type triggerV2IfVar = { type: 'v2If', - condition: '='|'!='|'>'|'<'|'>='|'<='|'null'|'true'|'false', + condition: '='|'!='|'>'|'<'|'>='|'<=', targetType: 'var'|'value', target: string, source: string, @@ -678,9 +679,36 @@ export type triggerV2Wait = { indent: number } +export type triggerV2IfAdvanced = { + type: 'v2IfAdvanced', + condition: '='|'!='|'>'|'<'|'>='|'<='|'≒'|'∋'|'∈'|'∌'|'∉'|'≡' + targetType: 'var'|'value', + target: string, + sourceType: 'var'|'value', + source: string, + indent: number +} + +export type triggerV2QuickSearchChat = { + type: 'v2QuickSearchChat', + value: string, + valueType: 'var'|'value', + condition: 'loose'|'strict'|'regex', + depth: string, + depthType: 'var'|'value', + outputVar: string, + indent: number +} + +export type triggerV2StopPromptSending = { + type: 'v2StopPromptSending', + indent: number +} + const safeSubset = [ 'v2SetVar', 'v2If', + 'v2IfAdvanced', 'v2Else', 'v2EndIndent', 'v2LoopNTimes', @@ -1169,8 +1197,9 @@ export async function runTrigger(char:character,mode:triggerMode, arg:{ setVar(varKey, resultValue) break } - case 'v2If':{ - const sourceValue = getVar(risuChatParser(effect.source,{chara:char})) + case 'v2If': + case 'v2IfAdvanced':{ + const sourceValue = (effect.type === 'v2If' || effect.sourceType === 'var') ? getVar(risuChatParser(effect.source,{chara:char})) : risuChatParser(effect.source,{chara:char}) const targetValue = effect.targetType === 'value' ? risuChatParser(effect.target,{chara:char}) : getVar(risuChatParser(effect.target,{chara:char})) let pass = false switch(effect.condition){ @@ -1208,6 +1237,60 @@ export async function runTrigger(char:character,mode:triggerMode, arg:{ pass = Number(sourceValue) <= Number(targetValue) break } + case '∈':{ + try { + pass = JSON.parse(targetValue).includes(sourceValue) + } catch (error) { + pass = false + } + break + } + case '∋':{ + try { + pass = JSON.parse(sourceValue).includes(targetValue) + } catch (error) { + pass = false + } + break + } + case '∉':{ + try { + pass = !JSON.parse(targetValue).includes(sourceValue) + } catch (error) { + pass = true + } + break + } + case '∌':{ + try { + pass = !JSON.parse(sourceValue).includes(targetValue) + } catch (error) { + pass = true + } + break + } + case '≒':{ + const num1 = Number(sourceValue) + const num2 = Number(targetValue) + if(Number.isNaN(num1) || Number.isNaN(num2)){ + pass = sourceValue.toLocaleLowerCase().replace(/ /g,'') === targetValue.toLocaleLowerCase().replace(/ /g,'') + } + else{ + pass = Math.abs(num1 - num2) < 0.0001 + } + break + } + case '≡':{ + if(targetValue === 'true'){ + pass = sourceValue === 'true' || sourceValue === '1' + } + else if(targetValue === 'false'){ + pass = !(sourceValue === 'true' || sourceValue === '1') + } + else{ + pass = sourceValue === targetValue + } + } } @@ -1814,6 +1897,29 @@ export async function runTrigger(char:character,mode:triggerMode, arg:{ setVar(effect.outputVar, json.length.toString()) break } + case 'v2QuickSearchChat':{ + const value = effect.valueType === 'value' ? risuChatParser(effect.value,{chara:char}) : getVar(risuChatParser(effect.value,{chara:char})) + const depth = effect.depthType === 'value' ? Number(risuChatParser(effect.depth,{chara:char})) : Number(getVar(risuChatParser(effect.depth,{chara:char}))) + const condition = effect.condition + + if(isNaN(depth)){ + setVar(effect.outputVar, '0') + break + } + let pass = false + let da = chat.message.slice(0-depth).map((v)=>v.data).join(' ') + if(condition === 'strict'){ + pass = da.split(' ').includes(value) + } + else if(condition === 'loose'){ + pass = da.toLowerCase().includes(value.toLowerCase()) + } + else if(condition === 'regex'){ + pass = new RegExp(value).test(da) + } + setVar(effect.outputVar, pass ? '1' : '0') + break + } } } }