feat: add image translation feature and enhance regex list functionality

This commit is contained in:
Kwaroran
2024-12-27 15:51:29 +09:00
parent c5f5786af7
commit 191be6d5c1
12 changed files with 376 additions and 69 deletions

View File

@@ -836,4 +836,5 @@ export const languageEnglish = {
home: "Home", home: "Home",
showSavingIcon: "Show Saving Icon", showSavingIcon: "Show Saving Icon",
pluginVersionWarn: "This is {{plugin_version}} version of the plugin. which is not compatible with this version of RisuAI. please update the plugin to {{required_version}} version.", pluginVersionWarn: "This is {{plugin_version}} version of the plugin. which is not compatible with this version of RisuAI. please update the plugin to {{required_version}} version.",
imageTranslation: "Image Translation",
} }

View File

@@ -472,6 +472,9 @@
<span class="text-textcolor2 text-sm">{language.risuMDesc}</span> <span class="text-textcolor2 text-sm">{language.risuMDesc}</span>
{:else if $alertStore.submsg === 'preset'} {:else if $alertStore.submsg === 'preset'}
<span class="text-textcolor2 text-sm">{language.risupresetDesc}</span> <span class="text-textcolor2 text-sm">{language.risupresetDesc}</span>
{#if cardExportType2 === 'preset' && (DBState.db.botPresets[DBState.db.botPresetsId].image || DBState.db.botPresets[DBState.db.botPresetsId].regex?.length > 0)}
<span class="text-red-500 text-sm">Use RisuRealm to share the preset. Preset with image or regexes cannot be exported for now.</span>
{/if}
{:else} {:else}
<span class="text-textcolor2 text-sm">{language.ccv3Desc}</span> <span class="text-textcolor2 text-sm">{language.ccv3Desc}</span>
{#if cardExportType2 !== 'charx' && isCharacterHasAssets(DBState.db.characters[$selectedCharID])} {#if cardExportType2 !== 'charx' && isCharacterHasAssets(DBState.db.characters[$selectedCharID])}

View File

@@ -2,9 +2,216 @@
import { language } from "src/lang"; import { language } from "src/lang";
import TextInput from "../UI/GUI/TextInput.svelte"; import TextInput from "../UI/GUI/TextInput.svelte";
import TextAreaInput from "../UI/GUI/TextAreaInput.svelte"; import TextAreaInput from "../UI/GUI/TextAreaInput.svelte";
import Button from "../UI/GUI/Button.svelte";
import { selectSingleFile } from "src/ts/util";
import { requestChatData } from "src/ts/process/request";
import { alertError } from "src/ts/alert";
let selLang = $state("en"); let selLang = $state("en");
let prompt = $state(""); let prompt = $state('extract text chunk from the image, with all the positions and background color, and translate them to {{slot}} in a JSON format.Format of: \n\n [\n {\n "bg_hex_color": string\n "content": string\n "text_hex_color": string,\n "x_max": number,\n "x_min": number,\n "y_max": number,\n "y_min": number\n "translation": string,\n }\n]\n\n each properties is:\n - x_min, y_min, x_max, y_max: range of 0 (most left/top point of the image) to 1 (most bottom/right point of the image), it is the bounding boxes of the original text chunk.\n - bg_hex_color is the color of the background.\n - text_hex_color is the color of the text.\n - translation is the translated text.\n - content is the original text chunk.');
let canvas: HTMLCanvasElement;
let ctx: CanvasRenderingContext2D;
let inputImage: HTMLImageElement;
let output = $state('')
let loading = $state(false);
let aspectRatio = 1;
async function imageTranslate() {
if(loading){
return;
}
loading = true;
try {
const file = await selectSingleFile(['png', 'jpg', 'jpeg','gif','webp','avif']);
if (!file){
loading = false;
return;
};
if(!ctx){
ctx = canvas.getContext('2d');
}
const img = new Image();
inputImage = img;
img.src = URL.createObjectURL(new Blob([file.data]));
await img.decode();
aspectRatio = img.width / img.height;
canvas.width = img.width;
canvas.height = img.height;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
const data = canvas.toDataURL('image/png');
const schema = {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"type": "ARRAY",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"y_min": {
"type": "number"
},
"x_min": {
"type": "number"
},
"y_max": {
"type": "number"
},
"x_max": {
"type": "number"
},
"bg_hex_color": {
"type": "string"
},
"text_hex_color": {
"type": "string"
},
"content": {
"type": "string"
},
"translation": {
"type": "string"
}
},
"required": [
"y_min",
"x_min",
"y_max",
"x_max",
"content",
"translation",
"bg_hex_color",
"text_hex_color"
]
},
}
const d = await requestChatData({
formated: [{
role: 'user',
content: prompt.replace('{{slot}}', selLang),
multimodals: [{
type: 'image',
base64: data,
}],
}],
bias: {},
schema: JSON.stringify(schema)
}, 'translate')
if(d.type === 'streaming' || d.type === 'multiline'){
loading = false;
return alertError('This model is not supported in the playground')
}
if(d.type !== 'success'){
alertError(d.result)
}
output = d.result
output = JSON.stringify(JSON.parse(d.result), null, 2);
loading = false;
render()
} catch (error) {
alertError(JSON.stringify(error))
} finally {
loading = false;
}
}
async function render() {
if(!inputImage){
return
}
if(!ctx){
ctx = canvas.getContext('2d');
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(inputImage, 0, 0, canvas.width, canvas.height);
const data = JSON.parse(output);
for (const item of data) {
let [x_min, y_min, x_max, y_max] = [item.x_min, item.y_min, item.x_max, item.y_max];
if(x_min <= 1){
x_min *= canvas.width;
y_min *= canvas.height;
x_max *= canvas.width;
y_max *= canvas.height;
}
ctx.fillStyle = item.bg_hex_color;
ctx.fillRect(x_min, y_min, x_max - x_min, y_max - y_min);
// ctx.fillStyle = item.text_hex_color;
// ctx.fillText(item.translation, x_min, y_min);
//make text wrap, and fit the text in the box
const text = item.translation;
const maxWidth = x_max - x_min;
const maxHeight = y_max - y_min;
const textSizes = [288, 216, 192, 144, 120, 108, 96, 84, 76, 72, 68, 64, 60, 56, 52, 48, 44, 40, 36, 32, 28, 24, 20, 18, 16, 14, 12, 10];
let lineHeight = 0;
for(let i = 0; i < textSizes.length; i++){
ctx.font = `${textSizes[i]}px Arial`;
lineHeight = textSizes[i] * 1.2;
const lines = text.split('\n');
let totalHeight = 0;
let x = 0
for (let n = 0; n < lines.length; n++) {
let testLine = lines[n];
let metrics = ctx.measureText(testLine);
let testWidth = metrics.width;
x += testWidth;
if(testWidth > maxWidth){
totalHeight = maxHeight + 1;
break
}
if(x > maxWidth){
totalHeight += lineHeight;
x = testWidth
}
}
console.log(x, maxWidth, totalHeight, maxHeight, textSizes[i])
if(totalHeight < maxHeight){
break;
}
}
let words = text.split(' ');
let line = '';
let y = y_min + lineHeight;
for (let n = 0; n < words.length; n++) {
let testLine = line + words[n] + ' ';
let metrics = ctx.measureText(testLine);
let testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
ctx.fillStyle = item.text_hex_color;
ctx.fillText(line, x_min, y);
line = words[n] + ' ';
y += lineHeight;
} else {
line = testLine;
}
}
ctx.fillStyle = item.text_hex_color;
ctx.fillText(line, x_min, y);
}
console.log('rendered')
}
</script> </script>
@@ -13,3 +220,32 @@
<span class="text-textcolor text-lg mt-4">{language.prompt}</span> <span class="text-textcolor text-lg mt-4">{language.prompt}</span>
<TextAreaInput bind:value={prompt} /> <TextAreaInput bind:value={prompt} />
<Button className="mt-4" onclick={imageTranslate}>
{language.imageTranslation}
</Button>
{#if output}
<span class="text-textcolor text-lg mt-4">JSON</span>
<TextAreaInput bind:value={output} className="overflow-x-auto" onchange={render} />
{/if}
<canvas class="mt-2" bind:this={canvas} class:blur-effect={loading}></canvas>
<style>
.blur-effect {
filter: blur(5px);
animation: blur-animation 1s infinite alternate;
}
@keyframes blur-animation {
0% {
filter: blur(5px);
}
50% {
filter: blur(10px);
}
100% {
filter: blur(5px);
}
}
</style>

View File

@@ -15,6 +15,7 @@
import ToolConvertion from "./ToolConvertion.svelte"; import ToolConvertion from "./ToolConvertion.svelte";
import { joinMultiuserRoom } from "src/ts/sync/multiuser"; import { joinMultiuserRoom } from "src/ts/sync/multiuser";
import PlaygroundSubtitle from "./PlaygroundSubtitle.svelte"; import PlaygroundSubtitle from "./PlaygroundSubtitle.svelte";
import PlaygroundImageTrans from "./PlaygroundImageTrans.svelte";
let easterEggTouch = $state(0) let easterEggTouch = $state(0)
@@ -89,6 +90,11 @@
}}> }}>
<h1 class="text-2xl font-bold text-start">{language.subtitles}</h1> <h1 class="text-2xl font-bold text-start">{language.subtitles}</h1>
</button> </button>
<button class="bg-darkbg rounded-md p-6 flex flex-col transition-shadow hover:ring-1" onclick={() => {
PlaygroundStore.set(10)
}}>
<h1 class="text-2xl font-bold text-start">{language.imageTranslation}</h1>
</button>
<button class="bg-darkbg rounded-md p-6 flex flex-col transition-shadow hover:ring-1" onclick={() => { <button class="bg-darkbg rounded-md p-6 flex flex-col transition-shadow hover:ring-1" onclick={() => {
PlaygroundStore.set(101) PlaygroundStore.set(101)
}}> }}>
@@ -148,6 +154,9 @@
{#if $PlaygroundStore === 9} {#if $PlaygroundStore === 9}
<PlaygroundSubtitle/> <PlaygroundSubtitle/>
{/if} {/if}
{#if $PlaygroundStore === 10}
<PlaygroundImageTrans/>
{/if}
{#if $PlaygroundStore === 101} {#if $PlaygroundStore === 101}
<ToolConvertion/> <ToolConvertion/>
{/if} {/if}

View File

@@ -4,7 +4,8 @@
import Sortable from "sortablejs"; import Sortable from "sortablejs";
import { sleep, sortableOptions } from "src/ts/util"; import { sleep, sortableOptions } from "src/ts/util";
import { onDestroy, onMount } from "svelte"; import { onDestroy, onMount } from "svelte";
import { PlusIcon } from "lucide-svelte"; import { DownloadIcon, FolderUpIcon, PlusIcon } from "lucide-svelte";
import { exportRegex, importRegex } from "src/ts/process/scripts";
interface Props { interface Props {
value?: customscript[]; value?: customscript[];
buttons?: boolean buttons?: boolean
@@ -78,14 +79,22 @@
</div> </div>
{/key} {/key}
{#if buttons} {#if buttons}
<button class="w-full mt-2 rounded-md text-textcolor2 hover:text-textcolor focus-within:text-textcolor" onclick={() => { <div class="flex gap-2 mt-2">
value.push({ <button class="rounded-md text-textcolor2 hover:text-textcolor focus-within:text-textcolor" onclick={() => {
comment: "", value.push({
in: "", comment: "",
out: "", in: "",
type: "editinput" out: "",
}) type: "editinput"
}}> })
<PlusIcon /> }}>
</button> <PlusIcon />
</button>
<button class="rounded-md text-textcolor2 hover:text-textcolor focus-within:text-textcolor" onclick={() => {
exportRegex(value)
}}><DownloadIcon /></button>
<button class="rounded-md text-textcolor2 hover:text-textcolor focus-within:text-textcolor" onclick={async () => {
value = await importRegex(value)
}}><FolderUpIcon /></button>
</div>
{/if} {/if}

View File

@@ -547,10 +547,40 @@ export async function loadData() {
else{ else{
await forageStorage.Init() await forageStorage.Init()
LoadingStatusState.text = "Loading Local Save File..."
let gotStorage:Uint8Array = await forageStorage.getItem('database/database.bin') as unknown as Uint8Array
LoadingStatusState.text = "Decoding Local Save File..."
if(checkNullish(gotStorage)){
gotStorage = encodeRisuSaveLegacy({})
await forageStorage.setItem('database/database.bin', gotStorage)
}
try {
const decoded = await decodeRisuSave(gotStorage)
console.log(decoded)
setDatabase(decoded)
} catch (error) {
console.error(error)
const backups = await getDbBackups()
let backupLoaded = false
for(const backup of backups){
try {
LoadingStatusState.text = `Reading Backup File ${backup}...`
const backupData:Uint8Array = await forageStorage.getItem(`database/dbbackup-${backup}.bin`) as unknown as Uint8Array
setDatabase(
await decodeRisuSave(backupData)
)
backupLoaded = true
} catch (error) {}
}
if(!backupLoaded){
throw "Your save file is corrupted"
}
}
if(await forageStorage.checkAccountSync()){ if(await forageStorage.checkAccountSync()){
LoadingStatusState.text = "Checking Account Sync..." LoadingStatusState.text = "Checking Account Sync..."
let gotStorage:Uint8Array = await (forageStorage.realStorage as AccountStorage).getItem('database/database.bin', (v) => { let gotStorage:Uint8Array = await (forageStorage.realStorage as AccountStorage).getItem('database/database.bin', (v) => {
LoadingStatusState.text = `Loading Save File ${(v*100).toFixed(2)}%` LoadingStatusState.text = `Loading Remote Save File ${(v*100).toFixed(2)}%`
}) })
if(checkNullish(gotStorage)){ if(checkNullish(gotStorage)){
gotStorage = encodeRisuSaveLegacy({}) gotStorage = encodeRisuSaveLegacy({})
@@ -578,37 +608,8 @@ export async function loadData() {
} }
} }
} }
else{ LoadingStatusState.text = "Rechecking Account Sync..."
LoadingStatusState.text = "Loading Save File..." await forageStorage.checkAccountSync()
let gotStorage:Uint8Array = await forageStorage.getItem('database/database.bin') as unknown as Uint8Array
LoadingStatusState.text = "Decoding Save File..."
if(checkNullish(gotStorage)){
gotStorage = encodeRisuSaveLegacy({})
await forageStorage.setItem('database/database.bin', gotStorage)
}
try {
const decoded = await decodeRisuSave(gotStorage)
console.log(decoded)
setDatabase(decoded)
} catch (error) {
console.error(error)
const backups = await getDbBackups()
let backupLoaded = false
for(const backup of backups){
try {
LoadingStatusState.text = `Reading Backup File ${backup}...`
const backupData:Uint8Array = await forageStorage.getItem(`database/dbbackup-${backup}.bin`) as unknown as Uint8Array
setDatabase(
await decodeRisuSave(backupData)
)
backupLoaded = true
} catch (error) {}
}
if(!backupLoaded){
throw "Your save file is corrupted"
}
}
}
LoadingStatusState.text = "Checking Drive Sync..." LoadingStatusState.text = "Checking Drive Sync..."
const isDriverMode = await checkDriverInit() const isDriverMode = await checkDriverInit()
if(isDriverMode){ if(isDriverMode){

View File

@@ -19,7 +19,7 @@ import {createParser} from 'eventsource-parser'
import {Ollama} from 'ollama/dist/browser.mjs' import {Ollama} from 'ollama/dist/browser.mjs'
import { applyChatTemplate } from "./templates/chatTemplate"; import { applyChatTemplate } from "./templates/chatTemplate";
import { OobaParams } from "./prompt"; import { OobaParams } from "./prompt";
import { extractJSON, getOpenAIJSONSchema } from "./templates/jsonSchema"; import { extractJSON, getGeneralJSONSchema, getOpenAIJSONSchema } from "./templates/jsonSchema";
import { getModelInfo, LLMFlags, LLMFormat, type LLMModel } from "../model/modellist"; import { getModelInfo, LLMFlags, LLMFormat, type LLMModel } from "../model/modellist";
@@ -39,6 +39,8 @@ interface requestDataArgument{
continue?:boolean continue?:boolean
chatId?:string chatId?:string
noMultiGen?:boolean noMultiGen?:boolean
schema?:string
extractJson?:string
} }
interface RequestDataArgumentExtended extends requestDataArgument{ interface RequestDataArgumentExtended extends requestDataArgument{
@@ -357,6 +359,7 @@ export async function requestChatDataMain(arg:requestDataArgument, model:ModelMo
targ.abortSignal = abortSignal targ.abortSignal = abortSignal
targ.modelInfo = getModelInfo(targ.aiModel) targ.modelInfo = getModelInfo(targ.aiModel)
targ.mode = model targ.mode = model
targ.extractJson = arg.extractJson ?? db.extractJson
if(targ.aiModel === 'reverse_proxy'){ if(targ.aiModel === 'reverse_proxy'){
targ.modelInfo.internalID = db.customProxyRequestModel targ.modelInfo.internalID = db.customProxyRequestModel
targ.modelInfo.format = db.customAPIFormat targ.modelInfo.format = db.customAPIFormat
@@ -694,10 +697,10 @@ async function requestOpenAI(arg:RequestDataArgumentExtended):Promise<requestDat
body.seed = db.generationSeed body.seed = db.generationSeed
} }
if(db.jsonSchemaEnabled){ if(db.jsonSchemaEnabled || arg.schema){
body.response_format = { body.response_format = {
"type": "json_schema", "type": "json_schema",
"json_schema": getOpenAIJSONSchema() "json_schema": getOpenAIJSONSchema(arg.schema)
} }
} }
@@ -862,9 +865,9 @@ async function requestOpenAI(arg:RequestDataArgumentExtended):Promise<requestDat
try { try {
const rawChunk = data.replace("data: ", "") const rawChunk = data.replace("data: ", "")
if(rawChunk === "[DONE]"){ if(rawChunk === "[DONE]"){
if(db.extractJson && db.jsonSchemaEnabled){ if(arg.extractJson && (db.jsonSchemaEnabled || arg.schema)){
for(const key in readed){ for(const key in readed){
const extracted = extractJSON(readed[key], db.extractJson) const extracted = extractJSON(readed[key], arg.extractJson)
JSONreaded[key] = extracted JSONreaded[key] = extracted
} }
console.log(JSONreaded) console.log(JSONreaded)
@@ -897,9 +900,9 @@ async function requestOpenAI(arg:RequestDataArgumentExtended):Promise<requestDat
} catch (error) {} } catch (error) {}
} }
} }
if(db.extractJson && db.jsonSchemaEnabled){ if(arg.extractJson && (db.jsonSchemaEnabled || arg.schema)){
for(const key in readed){ for(const key in readed){
const extracted = extractJSON(readed[key], db.extractJson) const extracted = extractJSON(readed[key], arg.extractJson)
JSONreaded[key] = extracted JSONreaded[key] = extracted
} }
console.log(JSONreaded) console.log(JSONreaded)
@@ -974,12 +977,12 @@ async function requestOpenAI(arg:RequestDataArgumentExtended):Promise<requestDat
if(res.ok){ if(res.ok){
try { try {
if(arg.multiGen && dat.choices){ if(arg.multiGen && dat.choices){
if(db.extractJson && db.jsonSchemaEnabled){ if(arg.extractJson && (db.jsonSchemaEnabled || arg.schema)){
const c = dat.choices.map((v:{ const c = dat.choices.map((v:{
message:{content:string} message:{content:string}
}) => { }) => {
const extracted = extractJSON(v.message.content, db.extractJson) const extracted = extractJSON(v.message.content, arg.extractJson)
return ["char",extracted] return ["char",extracted]
}) })
@@ -999,10 +1002,10 @@ async function requestOpenAI(arg:RequestDataArgumentExtended):Promise<requestDat
} }
if(dat?.choices[0]?.text){ if(dat?.choices[0]?.text){
if(db.extractJson && db.jsonSchemaEnabled){ if(arg.extractJson && (db.jsonSchemaEnabled || arg.schema)){
try { try {
const parsed = JSON.parse(dat.choices[0].text) const parsed = JSON.parse(dat.choices[0].text)
const extracted = extractJSON(parsed, db.extractJson) const extracted = extractJSON(parsed, arg.extractJson)
return { return {
type: 'success', type: 'success',
result: extracted result: extracted
@@ -1020,10 +1023,10 @@ async function requestOpenAI(arg:RequestDataArgumentExtended):Promise<requestDat
result: dat.choices[0].text result: dat.choices[0].text
} }
} }
if(db.extractJson && db.jsonSchemaEnabled){ if(arg.extractJson && (db.jsonSchemaEnabled || arg.schema)){
return { return {
type: 'success', type: 'success',
result: extractJSON(dat.choices[0].message.content, db.extractJson) result: extractJSON(dat.choices[0].message.content, arg.extractJson)
} }
} }
const msg:OpenAIChatFull = (dat.choices[0].message) const msg:OpenAIChatFull = (dat.choices[0].message)
@@ -1693,6 +1696,13 @@ async function requestGoogleCloudVertex(arg:RequestDataArgumentExtended):Promise
} }
} }
if(db.jsonSchemaEnabled || arg.schema){
body.generation_config.response_mime_type = "application/json"
body.generation_config.response_schema = getGeneralJSONSchema(arg.schema, ['$schema','additionalProperties'])
console.log(body.generation_config.response_schema)
}
let url = '' let url = ''
if(arg.customURL){ if(arg.customURL){
@@ -1756,6 +1766,13 @@ async function requestGoogleCloudVertex(arg:RequestDataArgumentExtended):Promise
} }
} }
if(arg.extractJson && (db.jsonSchemaEnabled || arg.schema)){
for(let i=0;i<rDatas.length;i++){
const extracted = extractJSON(rDatas[i], arg.extractJson)
rDatas[i] = extracted
}
}
if(rDatas.length > 1){ if(rDatas.length > 1){
const thought = rDatas.splice(rDatas.length-2, 1)[0] const thought = rDatas.splice(rDatas.length-2, 1)[0]
rDatas[rDatas.length-1] = `<Thoughts>${thought}</Thoughts>\n\n${rDatas.join('\n\n')}` rDatas[rDatas.length-1] = `<Thoughts>${thought}</Thoughts>\n\n${rDatas.join('\n\n')}`
@@ -1827,6 +1844,12 @@ async function requestGoogleCloudVertex(arg:RequestDataArgumentExtended):Promise
processDataItem(res.data) processDataItem(res.data)
} }
if(arg.extractJson && (db.jsonSchemaEnabled || arg.schema)){
for(let i=0;i<rDatas.length;i++){
const extracted = extractJSON(rDatas[i], arg.extractJson)
rDatas[i] = extracted
}
}
if(rDatas.length > 1){ if(rDatas.length > 1){
const thought = rDatas.splice(rDatas.length-2, 1)[0] const thought = rDatas.splice(rDatas.length-2, 1)[0]
@@ -2539,7 +2562,7 @@ async function requestClaude(arg:RequestDataArgumentExtended):Promise<requestDat
break break
} }
text += "Error:" + JSON.parse(e.data).error?.message text += "Error:" + JSON.parse(e.data).error?.message
if(db.extractJson && db.jsonSchemaEnabled){ if(arg.extractJson && (db.jsonSchemaEnabled || arg.schema)){
controller.enqueue({ controller.enqueue({
"0": extractJSON(text, db.jsonSchema) "0": extractJSON(text, db.jsonSchema)
}) })
@@ -2618,7 +2641,7 @@ async function requestClaude(arg:RequestDataArgumentExtended):Promise<requestDat
result: JSON.stringify(res.data) result: JSON.stringify(res.data)
} }
} }
if(db.extractJson && db.jsonSchemaEnabled){ if(arg.extractJson && db.jsonSchemaEnabled){
return { return {
type: 'success', type: 'success',
result: extractJSON(resText, db.jsonSchema) result: extractJSON(resText, db.jsonSchema)

View File

@@ -522,7 +522,6 @@ export async function generateAIImage(genPrompt:string, currentChar:character, n
if(!auth){ if(!auth){
db.account = JSON.parse(localStorage.getItem("fallbackRisuToken")) db.account = JSON.parse(localStorage.getItem("fallbackRisuToken"))
auth = db?.account?.token auth = db?.account?.token
db.account.useSync = true
} }
const da = await globalFetch(keiServerURL() + '/imaggen', { const da = await globalFetch(keiServerURL() + '/imaggen', {
body: { body: {

View File

@@ -120,14 +120,33 @@ export function convertInterfaceToSchema(int:string){
return schema return schema
} }
export function getOpenAIJSONSchema(){ export function getOpenAIJSONSchema(schema?:string){
const db = getDatabase() const db = getDatabase()
const schema = { return {
"name": "format", "name": "format",
"strict": db.strictJsonSchema, "strict": db.strictJsonSchema,
"schema": convertInterfaceToSchema(db.jsonSchema) "schema": convertInterfaceToSchema(schema ?? db.jsonSchema)
} }
return schema }
export function getGeneralJSONSchema(schema?:string, excludes:string[] = []){
const db = getDatabase()
function process(data:any){
const keys = Object.keys(data)
for(const key of keys){
if(excludes.includes(key)){
delete data[key]
}
if(typeof data[key] === 'object'){
data[key] = process(data[key])
}
}
return data
}
const d = convertInterfaceToSchema(schema ?? db.jsonSchema)
return process(d)
} }
export function extractJSON(data:string, format:string){ export function extractJSON(data:string, format:string){

View File

@@ -176,9 +176,11 @@ export class AccountStorage{
const db = getDatabase() const db = getDatabase()
this.auth = db?.account?.token this.auth = db?.account?.token
if(!this.auth){ if(!this.auth){
db.account = JSON.parse(localStorage.getItem("fallbackRisuToken")) try {
this.auth = db?.account?.token db.account = JSON.parse(localStorage.getItem("fallbackRisuToken"))
db.account.useSync = true this.auth = db?.account?.token
db.account.useSync = true
} catch (error) {}
} }
} }

View File

@@ -46,7 +46,7 @@ export class AutoStorage{
if(localStorage.getItem('dosync') === 'avoid'){ if(localStorage.getItem('dosync') === 'avoid'){
return false return false
} }
if((localStorage.getItem('dosync') === 'sync' || db.account?.useSync) && (localStorage.getItem('accountst') !== 'able')){ if((localStorage.getItem('dosync') === 'sync' || db?.account?.useSync) && (localStorage.getItem('accountst') !== 'able')){
const keys = await this.realStorage.keys() const keys = await this.realStorage.keys()
let i = 0; let i = 0;
const accountStorage = new AccountStorage() const accountStorage = new AccountStorage()

View File

@@ -312,6 +312,11 @@ export class ChatTokenizer {
encoded += await this.tokenizeMultiModal(multimodal) encoded += await this.tokenizeMultiModal(multimodal)
} }
} }
if(data.thoughts && data.thoughts.length > 0){
for(const thought of data.thoughts){
encoded += (await encode(thought)).length + 1
}
}
return encoded return encoded
} }
async tokenizeChats(data:OpenAIChat[]){ async tokenizeChats(data:OpenAIChat[]){