feat: add prompt comparison feature

This commit is contained in:
poroyo
2025-01-05 14:20:20 +09:00
parent bf8bf02e2f
commit 8ba4417065
3 changed files with 210 additions and 3 deletions

View File

@@ -47,6 +47,7 @@
"core-js": "^3.35.0",
"cors": "^2.8.5",
"crc": "^4.3.2",
"diff": "^7.0.0",
"dompurify": "^3.0.8",
"eventsource-parser": "^1.1.2",
"exifr": "^7.1.3",

9
pnpm-lock.yaml generated
View File

@@ -101,6 +101,9 @@ importers:
crc:
specifier: ^4.3.2
version: 4.3.2(buffer@6.0.3)
diff:
specifier: ^7.0.0
version: 7.0.0
dompurify:
specifier: ^3.0.8
version: 3.0.8
@@ -1888,6 +1891,10 @@ packages:
resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==}
engines: {node: '>=0.3.1'}
diff@7.0.0:
resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==}
engines: {node: '>=0.3.1'}
dir-glob@3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'}
@@ -5561,6 +5568,8 @@ snapshots:
diff@5.1.0: {}
diff@7.0.0: {}
dir-glob@3.0.1:
dependencies:
path-type: 4.0.0

View File

@@ -1,12 +1,14 @@
<script lang="ts">
import { alertCardExport, alertConfirm, alertError } from "../../ts/alert";
import { alertCardExport, alertConfirm, alertError, alertMd, alertWait } from "../../ts/alert";
import { language } from "../../lang";
import { changeToPreset, copyPreset, downloadPreset, importPreset } from "../../ts/storage/database.svelte";
import { changeToPreset, copyPreset, downloadPreset, importPreset, getDatabase } from "../../ts/storage/database.svelte";
import { DBState } from 'src/ts/stores.svelte';
import { CopyIcon, Share2Icon, PencilIcon, FolderUpIcon, PlusIcon, TrashIcon, XIcon } from "lucide-svelte";
import { CopyIcon, Share2Icon, PencilIcon, FolderUpIcon, PlusIcon, TrashIcon, XIcon, GitCompare } from "lucide-svelte";
import TextInput from "../UI/GUI/TextInput.svelte";
import { prebuiltPresets } from "src/ts/process/templates/templates";
import { ShowRealmFrameStore } from "src/ts/stores.svelte";
import type { PromptItem, PromptItemPlain, PromptItemChatML, PromptItemTyped, PromptItemAuthorNote, PromptItemChat } from "src/ts/process/prompt.ts";
import { diffWordsWithSpace, diffLines } from 'diff';
let editMode = $state(false)
interface Props {
@@ -15,6 +17,191 @@
let { close = () => {} }: Props = $props();
let diffMode = false
let selectedPrompts: string[] = []
let selectedDiffPreset = $state(-1)
function isPromptItemPlain(item: PromptItem): item is PromptItemPlain {
return (
item.type === 'plain' || item.type === 'jailbreak' || item.type === 'cot'
);
}
function isPromptItemChatML(item: PromptItem): item is PromptItemChatML {
return item.type === 'chatML'
}
function isPromptItemTyped(item: PromptItem): item is PromptItemTyped {
return (
item.type === 'persona' ||
item.type === 'description' ||
item.type === 'lorebook' ||
item.type === 'postEverything' ||
item.type === 'memory'
)
}
function isPromptItemAuthorNote(item: PromptItem): item is PromptItemAuthorNote {
return item.type === 'authornote'
}
function isPromptItemChat(item: PromptItem): item is PromptItemChat {
return item.type === 'chat'
}
function escapeHtml(text: string): string {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;')
.replace(/\//g, '&#x2F;')
.replace(/\\/g, '&#92;')
.replace(/`/g, '&#96;')
.replace(/ /g, ' \u200B')
.replace(/\n/g, '<br>')
}
function getPrompt(id: number): string {
const db = getDatabase()
const formated = safeStructuredClone(db.botPresets[id].promptTemplate)
let prompt = ''
for(let i=0;i<formated.length;i++){
const item = formated[i]
switch (true) {
case isPromptItemPlain(item):{
prompt += '## ' + (item.role ?? 'Unknown') + '; ' + item.type + '; ' + item.type2 + '\n'
prompt += '\n' + item.text.replaceAll('```', '\\`\\`\\`') + '\n\n'
break
}
case isPromptItemChatML(item):{
prompt += '## ' + item.type + '\n'
prompt += '\n' + item.text.replaceAll('```', '\\`\\`\\`') + '\n\n'
break
}
case isPromptItemTyped(item):{
prompt += '## ' + 'system' + '; ' + item.type + '\n'
if(item.innerFormat){
prompt += '\n' + item.innerFormat.replaceAll('```', '\\`\\`\\`') + '\n\n'
}
break
}
case isPromptItemAuthorNote(item):{
prompt += '## ' + 'system' + '; ' + item.type + '\n'
if(item.innerFormat){
prompt += '\n' + item.innerFormat.replaceAll('```', '\\`\\`\\`') + '\n\n'
}
break
}
case isPromptItemChat(item):{
prompt += '## ' + 'chat' + '; ' + item.type + '\n'
prompt += '\n' + item.rangeStart + ' - ' + item.rangeEnd + '\n\n'
break
}
}
}
return prompt
}
function checkDiff(prompt1: string, prompt2: string): string {
const lineDiffs = diffLines(prompt1, prompt2)
let resultHtml = '';
for (let i = 0; i < lineDiffs.length; i++) {
const linePart = lineDiffs[i]
if(linePart.removed){
const nextPart = lineDiffs[i + 1]
if(nextPart?.added){
resultHtml += `<div style="border-left: 4px solid blue; padding-left: 8px;">${highlightChanges(linePart.value, nextPart.value)}</div>`
i++;
}
else{
resultHtml += `<div style="color: red; background-color: #ffe6e6;">${escapeHtml(linePart.value)}</div>`
}
}
else if(linePart.added){
resultHtml += `<div style="color: green; background-color: #e6ffe6;">${escapeHtml(linePart.value)}</div>`
}
else{
resultHtml += `<div>${escapeHtml(linePart.value)}</div>`
}
}
if(lineDiffs.length === 1 && !lineDiffs[0].added && !lineDiffs[0].removed) {
resultHtml = `<div style="background-color: #4caf50; color: white; padding: 10px 20px; border-radius: 5px; text-align: center;">No differences detected.</div>` + resultHtml
}
else{
resultHtml = `<div style="background-color: #ff9800; color: white; padding: 10px 20px; border-radius: 5px; text-align: center;">Differences detected. Please review the changes.</div>` + resultHtml
}
return resultHtml
}
function highlightChanges(string1: string, string2: string) {
const charDiffs = diffWordsWithSpace(string1, string2)
return charDiffs
.map(charPart => {
const escapedText = escapeHtml(charPart.value)
if (charPart.added) {
return `<span style="color: green; background-color: #e6ffe6;">${escapedText}</span>`
}
else if(charPart.removed) {
return `<span style="color: red; background-color: #ffe6e6;">${escapedText}</span>`
}
else{
return escapedText
}
})
.join('')
}
function handleDiffMode(id: number) {
if (selectedDiffPreset === id) {
selectedDiffPreset = -1
selectedPrompts = []
diffMode = !diffMode
return
} else {
selectedDiffPreset = id
}
const prompt = getPrompt(id)
if(!prompt){
return
}
if(!diffMode){
selectedPrompts = [prompt]
}
else if(selectedPrompts.length === 0){
return
}
else{
alertWait("Loading...")
const result = checkDiff(selectedPrompts[0], prompt)
alertMd(result)
selectedDiffPreset = -1
selectedPrompts = []
}
diffMode = !diffMode
}
</script>
<div class="absolute w-full h-full z-40 bg-black bg-opacity-50 flex justify-center items-center">
@@ -47,6 +234,16 @@
<span>{preset.name}</span>
{/if}
<div class="flex-grow flex justify-end">
<div class="{selectedDiffPreset === i ? 'text-green-500' : 'text-textcolor2 hover:text-green-500'} cursor-pointer mr-2" role="button" tabindex="0" onclick={(e) => {
e.stopPropagation()
handleDiffMode(i)
}} onkeydown={(e) => {
if(e.key === 'Enter'){
e.currentTarget.click()
}
}}>
<GitCompare size={18}/>
</div>
<div class="text-textcolor2 hover:text-green-500 cursor-pointer mr-2" role="button" tabindex="0" onclick={(e) => {
e.stopPropagation()
copyPreset(i)