feat: reintroduce module integration (#756)

# PR Checklist
- [ ] Have you checked if it works normally in all models? *Ignore this
if it doesn't use models.*
- [ ] Have you checked if it works normally in all web, local, and node
hosted versions? If it doesn't, have you blocked it in those versions?
- [ ] Have you added type definitions?

# Description
This PR reintroduces the module integration feature and includes the
following changes:

1. Modules activated via integration, rather than global activation from
the module menu, are now visually distinguished with a separate color.

![image](https://github.com/user-attachments/assets/641cbd41-e858-4206-8007-66778d06a220)

2. When a module is globally activated after being enabled through
integration, it will no longer be applied twice.
3. Added a button in the developer tools to view a list of all currently
active modules.

![image](https://github.com/user-attachments/assets/02119ea0-4de7-4eae-90ac-dd9a20ddf2de)

![image](https://github.com/user-attachments/assets/05411ea9-35bc-4fca-abf5-14e464171483)


4. Fixed an issue where the `module_enabled` cbs was operating based on
module names instead of namespaces, contrary to the documentation. It
now also detects modules activated through integration.
5. Fixed issues where module integration did not properly sync with
module additions, modifications, and deletions.

Let me know if any further adjustments are needed!
This commit is contained in:
kwaroran
2025-02-13 02:24:01 +09:00
committed by GitHub
6 changed files with 60 additions and 15 deletions

View File

@@ -643,6 +643,10 @@
{/if} {/if}
</Arcodion> </Arcodion>
<Arcodion styled name={language.moduleIntergration} help="moduleIntergration">
<TextAreaInput bind:value={DBState.db.moduleIntergration} fullwidth height={"32"} autocomplete="off"/>
</Arcodion>
<Arcodion styled name={language.regexScript}> <Arcodion styled name={language.regexScript}>
<RegexList bind:value={DBState.db.presetRegex} buttons /> <RegexList bind:value={DBState.db.presetRegex} buttons />
</Arcodion> </Arcodion>

View File

@@ -4,13 +4,14 @@
import { DBState } from 'src/ts/stores.svelte'; import { DBState } from 'src/ts/stores.svelte';
import Button from "src/lib/UI/GUI/Button.svelte"; import Button from "src/lib/UI/GUI/Button.svelte";
import ModuleMenu from "src/lib/Setting/Pages/Module/ModuleMenu.svelte"; import ModuleMenu from "src/lib/Setting/Pages/Module/ModuleMenu.svelte";
import { exportModule, importModule, type RisuModule } from "src/ts/process/modules"; import { exportModule, importModule, refreshModules, type RisuModule } from "src/ts/process/modules";
import { DownloadIcon, Edit, TrashIcon, Globe, Share2Icon } from "lucide-svelte"; import { DownloadIcon, Edit, TrashIcon, Globe, Share2Icon } from "lucide-svelte";
import { v4 } from "uuid"; import { v4 } from "uuid";
import { tooltip } from "src/ts/gui/tooltip"; import { tooltip } from "src/ts/gui/tooltip";
import { alertCardExport, alertConfirm, alertError } from "src/ts/alert"; import { alertCardExport, alertConfirm, alertError } from "src/ts/alert";
import TextInput from "src/lib/UI/GUI/TextInput.svelte"; import TextInput from "src/lib/UI/GUI/TextInput.svelte";
import { ShowRealmFrameStore } from "src/ts/stores.svelte"; import { ShowRealmFrameStore } from "src/ts/stores.svelte";
import { onDestroy } from "svelte";
let tempModule:RisuModule = $state({ let tempModule:RisuModule = $state({
name: '', name: '',
description: '', description: '',
@@ -30,6 +31,12 @@
return score return score
}) })
} }
onDestroy(() => {
if(DBState.db.moduleIntergration){
refreshModules()
}
})
</script> </script>
{#if mode === 0} {#if mode === 0}
<h2 class="mb-2 text-2xl font-bold mt-2">{language.modules}</h2> <h2 class="mb-2 text-2xl font-bold mt-2">{language.modules}</h2>
@@ -48,9 +55,12 @@
<div class="pl-3 pt-3 text-left flex"> <div class="pl-3 pt-3 text-left flex">
<span class="text-lg">{rmodule.name}</span> <span class="text-lg">{rmodule.name}</span>
<div class="flex-grow flex justify-end"> <div class="flex-grow flex justify-end">
<button class={(!DBState.db.enabledModules.includes(rmodule.id)) ? <button class={(DBState.db.enabledModules.includes(rmodule.id)) ?
"text-textcolor2 hover:text-green-500 mr-2 cursor-pointer" : "mr-2 cursor-pointer text-blue-500" :
"mr-2 cursor-pointer text-blue-500" rmodule.namespace &&
DBState.db.moduleIntergration.split(',').map((s) => s.trim()).includes(rmodule.namespace) ?
"text-amber-500 hover:text-green-500 mr-2 cursor-pointer" :
"text-textcolor2 hover:text-green-500 mr-2 cursor-pointer"
} use:tooltip={language.enableGlobal} onclick={async (e) => { } use:tooltip={language.enableGlobal} onclick={async (e) => {
e.stopPropagation() e.stopPropagation()
if(DBState.db.enabledModules.includes(rmodule.id)){ if(DBState.db.enabledModules.includes(rmodule.id)){

View File

@@ -18,6 +18,7 @@
import { applyChatTemplate, chatTemplates } from "src/ts/process/templates/chatTemplate"; import { applyChatTemplate, chatTemplates } from "src/ts/process/templates/chatTemplate";
import OptionInput from "../UI/GUI/OptionInput.svelte"; import OptionInput from "../UI/GUI/OptionInput.svelte";
import { loadLoreBookV3Prompt } from "src/ts/process/lorebook.svelte"; import { loadLoreBookV3Prompt } from "src/ts/process/lorebook.svelte";
import { getModules } from "src/ts/process/modules";
let previewMode = $state('chat') let previewMode = $state('chat')
let previewJoin = $state('yes') let previewJoin = $state('yes')
@@ -281,6 +282,16 @@
}}>Match Sources</Button> }}>Match Sources</Button>
</Arcodion> </Arcodion>
<Button className="mt-2" onclick={() => {
const modules = getModules()
const html = `
${modules.map((v) => {
return `## ${v.name}\n\n\`\`\`\n${v.description}\n\`\`\`\n`
}).join('\n')}
`.trim()
alertMd(html)
}}>Preview Module</Button>
<Button className="mt-2" onclick={() => { <Button className="mt-2" onclick={() => {
alertMd(getRequestLog()) alertMd(getRequestLog())
}}>Request Log</Button> }}>Request Log</Button>

View File

@@ -10,7 +10,7 @@ import { SizeStore, selectedCharID } from './stores.svelte';
import { calcString } from './process/infunctions'; import { calcString } from './process/infunctions';
import { findCharacterbyId, getPersonaPrompt, getUserIcon, getUserName, parseKeyValue, sfc32, sleep, uuidtoNumber } from './util'; import { findCharacterbyId, getPersonaPrompt, getUserIcon, getUserName, parseKeyValue, sfc32, sleep, uuidtoNumber } from './util';
import { getInlayAsset } from './process/files/inlays'; import { getInlayAsset } from './process/files/inlays';
import { getModuleAssets, getModuleLorebooks } from './process/modules'; import { getModuleAssets, getModuleLorebooks, getModules } from './process/modules';
import type { OpenAIChat } from './process/index.svelte'; import type { OpenAIChat } from './process/index.svelte';
import hljs from 'highlight.js/lib/core' import hljs from 'highlight.js/lib/core'
import 'highlight.js/styles/atom-one-dark.min.css' import 'highlight.js/styles/atom-one-dark.min.css'
@@ -1531,16 +1531,13 @@ function basicMatcher (p1:string,matcherArg:matcherArg,vars:{[key:string]:string
return dateTimeFormat(arra[1],t) return dateTimeFormat(arra[1],t)
} }
case 'module_enabled':{ case 'module_enabled':{
const selchar = db.characters[get(selectedCharID)] const modules = getModules()
let enabledChatModules:string[] = [] for(const module of modules){
if(!selchar){ if(module.namespace === arra[1]){
enabledChatModules = selchar.chats[selchar.chatPage].modules ?? [] return '1'
}
} }
const moduleId = db.modules.find((f) => { return '0'
return f.name === arra[1]
}).id
return (db.enabledModules.includes(moduleId) || enabledChatModules.includes(moduleId)) ? '1' : '0'
} }
case 'filter':{ case 'filter':{
const array = parseArray(arra[1]) const array = parseArray(arra[1])

View File

@@ -268,7 +268,20 @@ function getModuleByIds(ids:string[]){
modules.push(module) modules.push(module)
} }
} }
return modules return deduplicateModuleById(modules)
}
function deduplicateModuleById(modules:RisuModule[]){
let ids:string[] = []
let newModules:RisuModule[] = []
for(let i=0;i<modules.length;i++){
if(ids.includes(modules[i].id)){
continue
}
ids.push(modules[i].id)
newModules.push(modules[i])
}
return newModules
} }
let lastModules = '' let lastModules = ''
@@ -280,6 +293,10 @@ export function getModules(){
if (currentChat){ if (currentChat){
ids = ids.concat(currentChat.modules ?? []) ids = ids.concat(currentChat.modules ?? [])
} }
if(db.moduleIntergration){
const intList = db.moduleIntergration.split(',').map((s) => s.trim())
ids = ids.concat(intList)
}
const idsJoined = ids.join('-') const idsJoined = ids.join('-')
if(lastModules === idsJoined){ if(lastModules === idsJoined){
return lastModuleData return lastModuleData
@@ -441,3 +458,8 @@ export function moduleUpdate(){
lastModuleIds = ids lastModuleIds = ids
} }
} }
export function refreshModules(){
lastModules = ''
lastModuleData = []
}

View File

@@ -115,6 +115,7 @@ $effect.root(() => {
DBState?.db?.characters?.[selIdState.selId]?.chats?.[DBState?.db?.characters?.[selIdState.selId]?.chatPage]?.modules?.length DBState?.db?.characters?.[selIdState.selId]?.chats?.[DBState?.db?.characters?.[selIdState.selId]?.chatPage]?.modules?.length
DBState?.db?.characters?.[selIdState.selId]?.hideChatIcon DBState?.db?.characters?.[selIdState.selId]?.hideChatIcon
DBState?.db?.characters?.[selIdState.selId]?.backgroundHTML DBState?.db?.characters?.[selIdState.selId]?.backgroundHTML
DBState?.db?.moduleIntergration
moduleUpdate() moduleUpdate()
}) })
}) })