feat: add translation feature and ban character set functionality
This commit is contained in:
@@ -7,7 +7,7 @@ import { v4 as uuidv4, v4 } from 'uuid';
|
||||
import { characterFormatUpdate } from "./characters"
|
||||
import { AppendableBuffer, BlankWriter, checkCharOrder, downloadFile, isNodeServer, isTauri, loadAsset, LocalWriter, openURL, readImage, saveAsset, VirtualWriter } from "./globalApi.svelte"
|
||||
import { SettingsMenuIndex, ShowRealmFrameStore, selectedCharID, settingsOpen } from "./stores.svelte"
|
||||
import { convertImage, hasher } from "./parser.svelte"
|
||||
import { checkImageType, convertImage, hasher } from "./parser.svelte"
|
||||
import { CCardLib, type CharacterCardV3, type LorebookEntry } from '@risuai/ccardlib'
|
||||
import { reencodeImage } from "./process/files/inlays"
|
||||
import { PngChunk } from "./pngChunk"
|
||||
@@ -1316,6 +1316,21 @@ export async function exportCharacterCard(char:character, type:'png'|'json'|'cha
|
||||
path = `assets/${type}/${itype}/${name}.${card.data.assets[i].ext}`
|
||||
}
|
||||
card.data.assets[i].uri = 'embeded://' + path
|
||||
const imageType = checkImageType(rData)
|
||||
if(imageType === 'PNG' && writer instanceof CharXWriter){
|
||||
const metadatas:Record<string,string> = {}
|
||||
const gen = PngChunk.readGenerator(rData)
|
||||
for await (const chunk of gen){
|
||||
if(!chunk || chunk instanceof AppendableBuffer){
|
||||
continue
|
||||
}
|
||||
metadatas[chunk.key] = chunk.value
|
||||
}
|
||||
if(Object.keys(metadatas).length > 0){
|
||||
const metaPath = `x_meta/${name}.json`
|
||||
await writer.write(metaPath, Buffer.from(JSON.stringify(metadatas, null, 4)), 6)
|
||||
}
|
||||
}
|
||||
await writer.write(path, Buffer.from(await convertImage(rData)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2261,4 +2261,36 @@ export class PerformanceDebugger{
|
||||
this.kv[key].push(...other.kv[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getLanguageCodes(){
|
||||
let languageCodes:{
|
||||
code: string
|
||||
name: string
|
||||
}[] = []
|
||||
|
||||
for(let i=0x41;i<=0x5A;i++){
|
||||
for(let j=0x41;j<=0x5A;j++){
|
||||
languageCodes.push({
|
||||
code: String.fromCharCode(i) + String.fromCharCode(j),
|
||||
name: ''
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
languageCodes = languageCodes.map(v => {
|
||||
return {
|
||||
code: v.code.toLocaleLowerCase(),
|
||||
name: new Intl.DisplayNames([
|
||||
DBState.db.language === 'cn' ? 'zh' : DBState.db.language
|
||||
], {
|
||||
type: 'language',
|
||||
fallback: 'none'
|
||||
}).of(v.code)
|
||||
}
|
||||
}).filter((a) => {
|
||||
return a.name
|
||||
}).sort((a, b) => a.name.localeCompare(b.name))
|
||||
|
||||
return languageCodes
|
||||
}
|
||||
@@ -26,7 +26,21 @@ class StreamChunkWriter{
|
||||
break
|
||||
}
|
||||
if(typeString === 'tEXt'){
|
||||
pos += 12 + len
|
||||
const endPos = 12 + len + pos
|
||||
//get key
|
||||
let key=''
|
||||
while(data[pos+8] !== 0){
|
||||
key += String.fromCharCode(data[pos+8])
|
||||
pos++
|
||||
if(pos === endPos || key.length > 6){
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if(key !== 'ccv3' && key !== 'chara'){
|
||||
await this.pushData(data.slice(pos,endPos))
|
||||
}
|
||||
pos = endPos
|
||||
}
|
||||
else{
|
||||
await this.pushData(data.slice(pos,pos+12+len))
|
||||
|
||||
@@ -41,7 +41,7 @@ export class CharXWriter{
|
||||
//do nothing, just to make compatible with other writer
|
||||
}
|
||||
|
||||
async write(key:string,data:Uint8Array|string){
|
||||
async write(key:string,data:Uint8Array|string, level?:0|1|2|3|4|5|6|7|8|9){
|
||||
console.log('write',key)
|
||||
let dat:Uint8Array
|
||||
if(typeof data === 'string'){
|
||||
@@ -52,7 +52,7 @@ export class CharXWriter{
|
||||
}
|
||||
this.writeEnd = false
|
||||
const file = new fflate.ZipDeflate(key, {
|
||||
level: 0
|
||||
level: level ?? 0
|
||||
});
|
||||
await this.zip.add(file)
|
||||
await file.push(dat, true)
|
||||
@@ -102,6 +102,9 @@ export class CharXReader{
|
||||
else if(file.name === 'module.risum'){
|
||||
this.moduleData = assetData
|
||||
}
|
||||
else if(file.name.endsWith('.json')){
|
||||
//do nothing
|
||||
}
|
||||
else{
|
||||
this.assetPromises.push((async () => {
|
||||
const assetId = await saveAsset(assetData)
|
||||
|
||||
@@ -226,6 +226,19 @@ export async function requestChatData(arg:requestDataArgument, model:ModelModeEx
|
||||
}
|
||||
}
|
||||
|
||||
if(da.type === 'success' && db.banCharacterset?.length > 0){
|
||||
for(const set of db.banCharacterset){
|
||||
const checkRegex = new RegExp(`\\p{Script=${set}}`, 'gu')
|
||||
|
||||
if(checkRegex.test(da.result)){
|
||||
return {
|
||||
type: 'fail',
|
||||
result: 'Banned character found'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(da.type !== 'fail' || da.noRetry){
|
||||
return da
|
||||
|
||||
@@ -467,6 +467,7 @@ export function setDatabase(data:Database){
|
||||
data.enableCustomFlags ??= false
|
||||
data.assetMaxDifference ??= 4
|
||||
data.showSavingIcon ??= false
|
||||
data.banCharacterset ??= []
|
||||
changeLanguage(data.language)
|
||||
setDatabaseLite(data)
|
||||
}
|
||||
@@ -866,6 +867,7 @@ export interface Database{
|
||||
pluginV2: RisuPlugin[]
|
||||
showSavingIcon:boolean
|
||||
presetRegex: customscript[]
|
||||
banCharacterset:string[]
|
||||
}
|
||||
|
||||
interface SeparateParameters{
|
||||
|
||||
@@ -17,7 +17,7 @@ let cache={
|
||||
trans: ['']
|
||||
}
|
||||
|
||||
const LLMCacheStorage = localforage.createInstance({
|
||||
export const LLMCacheStorage = localforage.createInstance({
|
||||
name: "LLMTranslateCache"
|
||||
})
|
||||
|
||||
@@ -25,10 +25,6 @@ let waitTrans = 0
|
||||
|
||||
export async function translate(text:string, reverse:boolean) {
|
||||
let db = getDatabase()
|
||||
const plug = await translatorPlugin(text, reverse ? db.translator: 'en', reverse ? 'en' : db.translator)
|
||||
if(plug){
|
||||
return plug.content
|
||||
}
|
||||
if(!reverse){
|
||||
const ind = cache.origin.indexOf(text)
|
||||
if(ind !== -1){
|
||||
@@ -112,7 +108,7 @@ async function translateMain(text:string, arg:{from:string, to:string, host:stri
|
||||
let db = getDatabase()
|
||||
if(db.translatorType === 'llm'){
|
||||
const tr = arg.to || 'en'
|
||||
return translateLLM(text, {to: tr})
|
||||
return translateLLM(text, {to: tr, from: arg.from})
|
||||
}
|
||||
if(db.translatorType === 'deepl'){
|
||||
const body = {
|
||||
@@ -197,12 +193,7 @@ async function translateMain(text:string, arg:{from:string, to:string, host:stri
|
||||
return result
|
||||
}
|
||||
|
||||
export async function translateVox(text:string) {
|
||||
const plug = await translatorPlugin(text, 'en', 'ja')
|
||||
if(plug){
|
||||
return plug.content
|
||||
}
|
||||
|
||||
export async function translateVox(text:string) {
|
||||
return jaTrans(text)
|
||||
}
|
||||
|
||||
@@ -245,7 +236,8 @@ export async function translateHTML(html: string, reverse:boolean, charArg:simpl
|
||||
}
|
||||
if(db.translatorType === 'llm'){
|
||||
const tr = db.translator || 'en'
|
||||
return translateLLM(html, {to: tr, regenerate})
|
||||
const from = db.translatorInputLanguage
|
||||
return translateLLM(html, {to: tr, from: from, regenerate})
|
||||
}
|
||||
const dom = new DOMParser().parseFromString(html, 'text/html');
|
||||
console.log(html)
|
||||
@@ -454,7 +446,7 @@ function needSuperChunkedTranslate(){
|
||||
return getDatabase().translatorType === 'deeplX'
|
||||
}
|
||||
|
||||
async function translateLLM(text:string, arg:{to:string, regenerate?:boolean}):Promise<string>{
|
||||
async function translateLLM(text:string, arg:{to:string, from:string, regenerate?:boolean}):Promise<string>{
|
||||
if(!arg.regenerate){
|
||||
const cacheMatch = await LLMCacheStorage.getItem(text)
|
||||
if(cacheMatch){
|
||||
@@ -472,7 +464,7 @@ async function translateLLM(text:string, arg:{to:string, regenerate?:boolean}):P
|
||||
const charIndex = get(selectedCharID)
|
||||
const currentChar = db.characters[charIndex]
|
||||
let translatorNote
|
||||
if (currentChar.type === "character") {
|
||||
if (currentChar?.type === "character") {
|
||||
translatorNote = currentChar.translatorNote ?? ""
|
||||
} else {
|
||||
translatorNote = ""
|
||||
@@ -480,12 +472,12 @@ async function translateLLM(text:string, arg:{to:string, regenerate?:boolean}):P
|
||||
|
||||
let formated:OpenAIChat[] = []
|
||||
let prompt = db.translatorPrompt || `You are a translator. translate the following html or text into {{slot}}. do not output anything other than the translation.`
|
||||
let parsedPrompt = parseChatML(prompt.replaceAll('{{slot}}', arg.to).replaceAll('{{solt::content}}', text).replaceAll('{{slot::tnote}}', translatorNote))
|
||||
let parsedPrompt = parseChatML(prompt.replaceAll('{{slot::from}}', arg.from).replaceAll('{{slot}}', arg.to).replaceAll('{{solt::content}}', text).replaceAll('{{slot::tnote}}', translatorNote))
|
||||
if(parsedPrompt){
|
||||
formated = parsedPrompt
|
||||
}
|
||||
else{
|
||||
prompt = prompt.replaceAll('{{slot}}', arg.to).replaceAll('{{slot::tnote}}', translatorNote)
|
||||
prompt = prompt.replaceAll('{{slot}}', arg.to).replaceAll('{{slot::tnote}}', translatorNote).replaceAll('{{slot::from}}', arg.from)
|
||||
formated = [
|
||||
{
|
||||
'role': 'system',
|
||||
|
||||
Reference in New Issue
Block a user