Merge from main (#37)

This commit is contained in:
kwaroran
2023-05-11 18:33:30 +09:00
committed by GitHub
18 changed files with 339 additions and 119 deletions

View File

@@ -5,4 +5,4 @@ A AIChat Client, but better.
Msi version: https://github.com/kwaroran/RisuAI-release/releases
Web version: http://risu.pages.dev/
Web version: http://risuai.xyz/

View File

@@ -3,16 +3,13 @@ export function onRequest(context) {
return drive(request, context.env);
}
const encodedRedirectUri = encodeURIComponent("https://risu.pages.dev/")
async function drive(request, env){
const url = new URL(request.url);
const encodedRedirectUri = url.host.includes('dev') ? encodeURIComponent("https://risu.pages.dev/") : encodeURIComponent("https://risuai.xyz/")
const headerE = {
"Access-Control-Allow-Origin": "https://risu.pages.dev",
"Access-Control-Allow-Headers": "*"
}
const headerE = {}
const params = url.searchParams
const code = params.get('code')

View File

@@ -38,7 +38,7 @@ async function fetchProxy(request) {
const status = originalResponse.status;
let newResponseHeaders = new Headers(responseHeaders);
newResponseHeaders.set('access-control-allow-origin', 'https://risu.pages.dev/');
newResponseHeaders.set('access-control-allow-origin', 'https://risuai.xyz/');
newResponseHeaders.set('access-control-allow-credentials', "true");
newResponseHeaders.delete('content-security-policy');
newResponseHeaders.delete('content-security-policy-report-only');

View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"cors-anywhere": "^0.4.4"
}
}

68
server/proxy/pnpm-lock.yaml generated Normal file
View File

@@ -0,0 +1,68 @@
lockfileVersion: 5.4
specifiers:
cors-anywhere: ^0.4.4
http-proxy: ^1.18.1
dependencies:
cors-anywhere: 0.4.4
http-proxy: 1.18.1
packages:
/cors-anywhere/0.4.4:
resolution: {integrity: sha512-8OBFwnzMgR4mNrAeAyOLB2EruS2z7u02of2bOu7i9kKYlZG+niS7CTHLPgEXKWW2NAOJWRry9RRCaL9lJRjNqg==}
engines: {node: '>=0.10.0'}
dependencies:
http-proxy: 1.11.1
proxy-from-env: 0.0.1
dev: false
/eventemitter3/1.2.0:
resolution: {integrity: sha512-DOFqA1MF46fmZl2xtzXR3MPCRsXqgoFqdXcrCVYM3JNnfUeHTm/fh/v/iU7gBFpwkuBmoJPAm5GuhdDfSEJMJA==}
dev: false
/eventemitter3/4.0.7:
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
dev: false
/follow-redirects/1.15.2:
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
dev: false
/http-proxy/1.11.1:
resolution: {integrity: sha512-qz7jZarkVG3G6GMq+4VRJPSN4NkIjL4VMTNhKGd8jc25BumeJjWWvnY3A7OkCGa8W1TTxbaK3dcE0ijFalITVA==}
engines: {node: '>=0.10.0'}
dependencies:
eventemitter3: 1.2.0
requires-port: 0.0.1
dev: false
/http-proxy/1.18.1:
resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==}
engines: {node: '>=8.0.0'}
dependencies:
eventemitter3: 4.0.7
follow-redirects: 1.15.2
requires-port: 1.0.0
transitivePeerDependencies:
- debug
dev: false
/proxy-from-env/0.0.1:
resolution: {integrity: sha512-B9Hnta3CATuMS0q6kt5hEezOPM+V3dgaRewkFtFoaRQYTVNsHqUvFXmndH06z3QO1ZdDnRELv5vfY6zAj/gG7A==}
dev: false
/requires-port/0.0.1:
resolution: {integrity: sha512-AzPDCliPoWDSvEVYRQmpzuPhGGEnPrQz9YiOEvn+UdB9ixBpw+4IOZWtwctmpzySLZTy7ynpn47V14H4yaowtA==}
dev: false
/requires-port/1.0.0:
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
dev: false

6
server/proxy/server.js Normal file
View File

@@ -0,0 +1,6 @@
const cors_proxy = require('cors-anywhere');
const host = process.env.HOST || 'localhost';
const port = process.env.PORT || 8080;
cors_proxy.createServer({originWhitelist: []}).listen(port, host, function() {console.log('Running Proxy on ' + host + ':' + port);});

View File

@@ -8,7 +8,7 @@
},
"package": {
"productName": "RisuAI",
"version": "0.8.0"
"version": "0.7.9"
},
"tauri": {
"allowlist": {

View File

@@ -1,8 +1,10 @@
<script>
import { ArrowBigLeftIcon } from "lucide-svelte";
import { changeLanguage, language } from "src/lang";
import { addDefaultCharacters } from "src/ts/characters";
import { addDefaultCharacters } from "src/ts/characters";
import { DataBase } from "src/ts/database";
import { sleep } from "src/ts/util";
let step = 0
let provider = 0
@@ -167,6 +169,8 @@
$DataBase.forceReplaceUrl2 = $DataBase.forceReplaceUrl
await addDefaultCharacters()
$DataBase.didFirstSetup = true
await sleep(2000)
location.reload()
}}> {language.confirm}</button>
</div>
{/if}

View File

@@ -476,9 +476,19 @@
<span class="text-neutral-200">{language.requestretrys}</span>
<input class="text-neutral-200 mb-4 p-2 bg-transparent input-text focus:bg-selected text-sm" type="number" min={0} max="20" bind:value={$DataBase.requestRetrys}>
<span class="text-neutral-200">Request Method</span>
<select class="bg-transparent input-text text-gray-200 appearance-none text-sm mb-4" bind:value={$DataBase.requestmet}>
<option value="normal" class="bg-darkbg appearance-none">Normal</option>
<option value="proxy" class="bg-darkbg appearance-none">Proxy</option>
<option value="plain" class="bg-darkbg appearance-none">Plain Fetch</option>
</select>
{#if isTauri}
<span class="text-neutral-200 mt-2">Request Lib</span>
{#if $DataBase.requestmet === 'proxy'}
<span class="text-neutral-200">Request Proxy URL</span>
<input class="text-neutral-200 mb-4 p-2 bg-transparent input-text focus:bg-selected text-sm" bind:value={$DataBase.requestproxy}>
{/if}
{#if isTauri && $DataBase.requestmet === 'normal'}
<span class="text-neutral-200">Request Lib</span>
<select class="bg-transparent input-text text-gray-200 appearance-none text-sm" bind:value={$DataBase.requester}>
<option value="new" class="bg-darkbg appearance-none">Reqwest</option>
<option value="old" class="bg-darkbg appearance-none">Tauri</option>

View File

@@ -21,19 +21,17 @@
function createScratch(){
reseter();
const cid = createNewCharacter()
selectedCharID.set(cid)
selectedCharID.set(-1)
}
function createGroup(){
reseter();
const cid = createNewGroup()
selectedCharID.set(cid)
selectedCharID.set(-1)
}
async function createImport(){
reseter();
const cid = await importCharacter()
if(cid){
selectedCharID.set(cid)
}
selectedCharID.set(-1)
}
function changeChar(index:number){

View File

@@ -11,28 +11,6 @@ import { characterFormatUpdate } from "./characters"
import { downloadFile, readImage } from "./globalApi"
import { cloneDeep } from "lodash"
type OfficialCardSpec = {
spec: 'chara_card_v2'
spec_version: '2.0' // May 8th addition
data: {
name: string
description: string
personality: string
scenario: string
first_mes: string
mes_example: string
creator_notes: string
system_prompt: string
post_history_instructions: string
alternate_greetings: string[]
character_book?: CharacterBook
tags: string[]
creator: string
character_version: number
extensions: Record<string, any>
}
}
type CharacterBook = null
export async function importCharacter() {
@@ -49,29 +27,7 @@ export async function importCharacter() {
}
if((da.char_name || da.name) && (da.char_persona || da.description) && (da.char_greeting || da.first_mes)){
let db = get(DataBase)
db.characters.push({
name: da.char_name ?? da.name,
firstMessage: da.char_greeting ?? da.first_mes,
desc: da.char_persona ?? da.description,
notes: '',
chats: [{
message: [],
note: '',
name: 'Chat 1',
localLore: []
}],
chatPage: 0,
image: '',
emotionImages: [],
bias: [],
globalLore: [],
viewScreen: 'none',
chaId: uuidv4(),
sdData: defaultSdDataFunc(),
utilityBot: false,
customscript: [],
exampleMessage: ''
})
db.characters.push(convertOldTavernAndJSON(da))
DataBase.set(db)
alertNormal(language.importedCharacter)
return
@@ -105,7 +61,6 @@ export async function importCharacter() {
return
}
let char:character = va.data
let db = get(DataBase)
if(char.emotionImages && char.emotionImages.length > 0){
@@ -140,38 +95,12 @@ export async function importCharacter() {
}
else if(readed.chara){
const charaData:OldTavernChar = JSON.parse(Buffer.from(readed.chara, 'base64').toString('utf-8'))
if(charaData.first_mes && charaData.name && charaData.description){
const imgp = await saveImage(PngMetadata.filter(img))
let db = get(DataBase)
db.characters.push({
name: charaData.name,
firstMessage: charaData.first_mes,
desc: charaData.description,
notes: '',
chats: [{
message: [],
note: '',
name: 'Chat 1',
localLore: []
}],
chatPage: 0,
image: imgp,
emotionImages: [],
bias: [],
globalLore: [],
viewScreen: 'none',
chaId: uuidv4(),
sdData: defaultSdDataFunc(),
utilityBot: false,
customscript: [],
exampleMessage: ''
})
DataBase.set(db)
alertNormal(language.importedCharacter)
return db.characters.length - 1
}
alertError(language.errors.noData)
return null
const imgp = await saveImage(PngMetadata.filter(img))
let db = get(DataBase)
db.characters.push(convertOldTavernAndJSON(charaData, imgp))
DataBase.set(db)
alertNormal(language.importedCharacter)
return db.characters.length - 1
}
else{
alertError(language.errors.noData)
@@ -183,6 +112,88 @@ export async function importCharacter() {
}
}
export async function characterHubImport() {
const charPath = (new URLSearchParams(location.search)).get('charahub')
try {
if(charPath){
const url = new URL(location.href);
url.searchParams.delete('charahub');
window.history.pushState(null, '', url.toString());
const chara = await fetch("https://api.characterhub.org/api/characters/download", {
method: "POST",
body: JSON.stringify({
"format": "tavern",
"fullPath": charPath,
"version": "main"
}),
headers: {
"content-type": "application/json"
}
})
const img = new Uint8Array(await chara.arrayBuffer())
const readed = (await exifr.parse(img, true))
{
const charaData:CharacterCardV2 = JSON.parse(Buffer.from(readed.chara, 'base64').toString('utf-8'))
if(await importSpecv2(charaData, img)){
return
}
}
{
const imgp = await saveImage(PngMetadata.filter(img))
let db = get(DataBase)
const charaData:OldTavernChar = JSON.parse(Buffer.from(readed.chara, 'base64').toString('utf-8'))
db.characters.push(convertOldTavernAndJSON(charaData, imgp))
DataBase.set(db)
alertNormal(language.importedCharacter)
return
}
}
} catch (error) {
alertError(language.errors.noData)
return null
}
}
function convertOldTavernAndJSON(charaData:OldTavernChar, imgp:string|undefined = undefined):character{
let desc = charaData.description ?? ''
if(charaData.personality){
desc += '\n\n' + charaData.personality
}
if(charaData.scenario){
desc += '\n\n' + charaData.scenario
}
return {
name: charaData.name ?? 'unknown name',
firstMessage: charaData.first_mes ?? 'unknown first message',
desc: desc,
notes: '',
chats: [{
message: [],
note: '',
name: 'Chat 1',
localLore: []
}],
chatPage: 0,
image: imgp,
emotionImages: [],
bias: [],
globalLore: [],
viewScreen: 'none',
chaId: uuidv4(),
sdData: defaultSdDataFunc(),
utilityBot: false,
customscript: [],
exampleMessage: charaData.mes_example
}
}
export async function exportChar(charaID:number) {
const db = get(DataBase)
@@ -335,7 +346,7 @@ async function importSpecv2(card:CharacterCardV2, img?:Uint8Array):Promise<boole
setDatabase(db)
alertNormal(language.importedCharacter)
return true
}
@@ -379,7 +390,7 @@ interface OldTavernChar{
create_date: string
description: string
first_mes: string
mes_example: "<START>"
mes_example: string
name: string
personality: ""
scenario: ""

View File

@@ -7,7 +7,8 @@ import { cloneDeep } from 'lodash';
export const DataBase = writable({} as any as Database)
export const loadedStore = writable(false)
export let appVer = '0.8.0'
export let appVer = '0.7.9'
export function setDatabase(data:Database){
if(checkNullish(data.characters)){
@@ -171,6 +172,12 @@ export function setDatabase(data:Database){
if(checkNullish(data.bias)){
data.bias = []
}
if(checkNullish(data.requestmet)){
data.requestmet = 'normal'
}
if(checkNullish(data.requestproxy)){
data.requestproxy = ''
}
if(checkNullish(data.sdConfig)){
data.sdConfig = {
width:512,
@@ -339,6 +346,8 @@ export interface Database{
emotionPrompt2:string
useSayNothing:boolean
didFirstSetup: boolean
requestmet: string
requestproxy: string
}

View File

@@ -10,7 +10,7 @@ import { open } from '@tauri-apps/api/shell';
export async function checkDriver(type:'save'|'load'|'loadtauri'|'savetauri'){
const CLIENT_ID = '580075990041-l26k2d3c0nemmqiu3d3aag01npfrkn76.apps.googleusercontent.com';
const REDIRECT_URI = 'https://risu.pages.dev/';
const REDIRECT_URI = isTauri ? "https://risuai.xyz/" : `https://${location.host}/`
const SCOPE = 'https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/drive.appdata';
const encodedRedirectUri = encodeURIComponent(REDIRECT_URI);
const authorizationUrl = `https://accounts.google.com/o/oauth2/auth?client_id=${CLIENT_ID}&redirect_uri=${encodedRedirectUri}&scope=${SCOPE}&response_type=code&state=${type}`;
@@ -45,7 +45,7 @@ export async function checkDriverInit() {
const code = loc.get('code')
if(code){
const res = await fetch(`https://aichandict.xyz/api/drive/access?code=${encodeURIComponent(code)}`)
const res = await fetch(`/drive?code=${encodeURIComponent(code)}`)
if(res.status >= 200 && res.status < 300){
const json:{
access_token:string,

View File

@@ -8,13 +8,14 @@ import { get } from "svelte/store";
import { DataBase, loadedStore, setDatabase, type Database, updateTextTheme, defaultSdDataFunc } from "./database";
import pako from "pako";
import { appWindow } from "@tauri-apps/api/window";
import { checkUpdate } from "./update";
import { checkOldDomain, checkUpdate } from "./update";
import { selectedCharID } from "./stores";
import { Body, ResponseType, fetch as TauriFetch } from "@tauri-apps/api/http";
import { loadPlugins } from "./process/plugins";
import { alertError, alertStore } from "./alert";
import { checkDriverInit } from "./drive/drive";
import { hasher } from "./parser";
import { characterHubImport } from "./characterCards";
//@ts-ignore
export const isTauri = !!window.__TAURI__
@@ -265,6 +266,10 @@ export async function loadData() {
else{
usingSw = false
}
checkOldDomain()
if(get(DataBase).didFirstSetup){
characterHubImport()
}
}
try {
await pargeChunks()
@@ -310,8 +315,84 @@ export async function globalFetch(url:string, arg:{body?:any,headers?:{[key:stri
}
}
if(isTauri){
if(db.requestmet === 'proxy'){
try {
let headers = arg.headers ?? {}
if(!headers["Content-Type"]){
headers["Content-Type"] = `application/json`
}
const furl = new URL(db.requestproxy)
furl.pathname = url
const da = await fetch(furl, {
body: JSON.stringify(arg.body),
headers: arg.headers,
method: method
})
if(arg.rawResponse){
addFetchLog("Uint8Array Response", da.ok)
return {
ok: da.ok,
data: new Uint8Array(await da.arrayBuffer())
}
}
else{
const dat = await da.json()
addFetchLog(dat, da.ok)
return {
ok: da.ok,
data: dat
}
}
} catch (error) {
return {
ok: false,
data: `${error}`,
}
}
}
if(db.requestmet === 'plain'){
try {
let headers = arg.headers ?? {}
if(!headers["Content-Type"]){
headers["Content-Type"] = `application/json`
}
const furl = new URL(url)
const da = await fetch(furl, {
body: JSON.stringify(arg.body),
headers: arg.headers,
method: method
})
if(arg.rawResponse){
addFetchLog("Uint8Array Response", da.ok)
return {
ok: da.ok,
data: new Uint8Array(await da.arrayBuffer())
}
}
else{
const dat = await da.json()
addFetchLog(dat, da.ok)
return {
ok: da.ok,
data: dat
}
}
} catch (error) {
return {
ok: false,
data: `${error}`,
}
}
}
if(isTauri){
if(db.requester === 'new'){
try {
let preHeader = arg.headers ?? {}
@@ -389,9 +470,8 @@ export async function globalFetch(url:string, arg:{body?:any,headers?:{[key:stri
headers["Content-Type"] = `application/json`
}
if(arg.rawResponse){
const furl = new URL("https://risu.pages.dev/proxy")
furl.searchParams.set("url", url)
const furl = `/proxy?url=${encodeURIComponent(url)}`
const da = await fetch(furl, {
body: JSON.stringify(arg.body),
headers: arg.headers,
@@ -405,9 +485,7 @@ export async function globalFetch(url:string, arg:{body?:any,headers?:{[key:stri
}
}
else{
const furl = new URL("https://risu.pages.dev/proxy")
furl.searchParams.set("url", url)
const furl = `/proxy?url=${encodeURIComponent(url)}`
const da = await fetch(furl, {
body: JSON.stringify(arg.body),
@@ -423,6 +501,7 @@ export async function globalFetch(url:string, arg:{body?:any,headers?:{[key:stri
}
}
} catch (error) {
console.log(error)
return {
ok:false,
data: `${error}`

View File

@@ -6,7 +6,7 @@ convertor.setOption('simpleLineBreaks', true);
export function ParseMarkdown(data:string) {
return DOMPurify.sanitize(convertor.makeHtml(data), {
FORBID_TAGS: ['a']
FORBID_TAGS: []
})
}

View File

@@ -112,6 +112,8 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
let bodyTemplate:any
const proompt = stringlizeChat(formated, currentChar.name)
const isNewAPI = DURL.includes('api')
const stopStrings = [`\nUser:`,`\nuser:`,`\n${db.username}:`]
if(isNewAPI){
bodyTemplate = {
'max_new_tokens': 80,
@@ -119,7 +121,7 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
'temperature': (db.temperature / 100),
'top_p': 0.9,
'typical_p': 1,
'repetition_penalty': (db.PresensePenalty / 100),
'repetition_penalty': db.PresensePenalty < 85 ? 0.85 : (db.PresensePenalty / 100),
'encoder_repetition_penalty': 1,
'top_k': 100,
'min_length': 0,
@@ -130,7 +132,7 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
'early_stopping': false,
'truncation_length': maxTokens,
'ban_eos_token': false,
'custom_stopping_strings': [`\nUser:`,`\nuser:`],
'stopping_strings': stopStrings,
'seed': -1,
add_bos_token: true,
prompt: proompt
@@ -145,7 +147,7 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
'temperature': (db.temperature / 100),
'top_p': 0.9,
'typical_p': 1,
'repetition_penalty': (db.PresensePenalty / 100),
'repetition_penalty': db.PresensePenalty < 85 ? 0.85 : (db.PresensePenalty / 100),
'encoder_repetition_penalty': 1,
'top_k': 100,
'min_length': 0,
@@ -156,7 +158,7 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
'early_stopping': false,
'truncation_length': maxTokens,
'ban_eos_token': false,
'custom_stopping_strings': [`\nUser:`,`\nuser:`],
'custom_stopping_strings': stopStrings,
'seed': -1,
add_bos_token: true,
}
@@ -175,15 +177,17 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
console.log(res.data)
if(res.ok){
try {
if(isNewAPI){
return {
type: 'success',
result: dat.results[0].text.substring(proompt.length)
let result:string = isNewAPI ? dat.results[0].text : dat.data[0].substring(proompt.length)
for(const stopStr of stopStrings){
if(result.endsWith(stopStr)){
result.substring(0,result.length - stopStr.length)
}
}
return {
type: 'success',
result: dat.data[0].substring(proompt.length)
result: result
}
} catch (error) {
return {

View File

@@ -1,10 +1,11 @@
import { fetch } from "@tauri-apps/api/http";
import { DataBase, appVer, setDatabase } from "./database";
import { alertConfirm } from "./alert";
import { alertConfirm, alertMd } from "./alert";
import { language } from "../lang";
import { get } from "svelte/store";
import {open} from '@tauri-apps/api/shell'
const isOldDomain = location.hostname.includes('pages.dev')
export async function checkUpdate(){
try {
@@ -48,4 +49,32 @@ function versionStringToNumber(versionString:string):number {
.map((component) => component.padStart(2, "0"))
.join("")
);
}
export function checkOldDomain(){
let db = get(DataBase)
if(isOldDomain){
if(!db.didFirstSetup){
location.href = 'https://risuai.xyz'
alertMd("Redirecting...")
return
}
if(db.language === 'ko'){
alertMd("# 웹 버전 RisuAI의 도메인이 **risuai.xyz**로 변경되었습니다"
+ "\n\nRisuAI의 현재 도메인 risu.pages.dev는 클라우드플레어의 기본 도메인으로, 뭔가를 하기 힘든 구조입니다. 그래서 도메인을 옮기기로 했습니다."
+ "\n\n현재 도메인인 risu.pages.dev은 언젠가 셧다운됩니다."
+ "\n\n**브라우저는 교차 도메인 저장소를 허용하지 않으므로 설정에서 Google 드라이브에 백업/로드해서 데이터를 옮기거나 새 도메인으로 수동으로 옮겨야합니다. 그렇지 않은 경우 이 도메인이 셧다운 될 때, 데이터는 영원히 사라집니다.**"
+ "\n\n[새로운 도메인 링크](https://risuai.xyz/)."
)
}
else{
alertMd("# Web version RisuAI's domain has transfered to **risuai.xyz**"
+ "\n\nRisuAI's domain risu.pages.dev is cloudflare's default domain name, which we can't do something freely. so we decided to move to another domain."
+ "\n\nThe current domain risu.pages.dev will be shut downed eventually."
+ "\n\n**Browsers doesn't allow cross-domain storage so you should migrate datas by backuping/loading to google drive in settings or migrate manually to new domain. if not, when this domain shuts down: YOUR DATA WILL BE GONE FOREVER.**"
+ "\n\n[Link to new domain](https://risuai.xyz/)."
)
}
return
}
}

View File

@@ -1 +1 @@
{"version":"0.8.0"}
{"version":"0.7.9"}