Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
chenpangpang
open-webui
Commits
d8a1d7eb
Commit
d8a1d7eb
authored
Jun 17, 2024
by
Timothy J. Baek
Browse files
feat: character utils
parent
2f7120a7
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
173 additions
and
0 deletions
+173
-0
src/lib/utils/characters/index.ts
src/lib/utils/characters/index.ts
+173
-0
No files found.
src/lib/utils/characters/index.ts
View file @
d8a1d7eb
import
CRC32
from
'
crc-32
'
;
export
const
parseFile
=
async
(
file
)
=>
{
if
(
file
.
type
===
'
application/json
'
)
{
return
await
parseJsonFile
(
file
);
}
else
if
(
file
.
type
===
'
image/png
'
)
{
return
await
parsePngFile
(
file
);
}
else
{
throw
new
Error
(
'
Unsupported file type
'
);
}
};
const
parseJsonFile
=
async
(
file
)
=>
{
const
text
=
await
file
.
text
();
const
json
=
JSON
.
parse
(
text
);
const
character
=
extractCharacter
(
json
);
return
{
file
,
json
,
formats
:
detectFormats
(
json
),
character
};
};
const
parsePngFile
=
async
(
file
)
=>
{
const
arrayBuffer
=
await
file
.
arrayBuffer
();
const
text
=
parsePngText
(
arrayBuffer
);
const
json
=
JSON
.
parse
(
text
);
const
image
=
URL
.
createObjectURL
(
file
);
const
character
=
extractCharacter
(
json
);
return
{
file
,
json
,
image
,
formats
:
detectFormats
(
json
),
character
};
};
const
parsePngText
=
(
arrayBuffer
)
=>
{
const
textChunkKeyword
=
'
chara
'
;
const
chunks
=
readPngChunks
(
new
Uint8Array
(
arrayBuffer
));
const
textChunk
=
chunks
.
filter
((
chunk
)
=>
chunk
.
type
===
'
tEXt
'
)
.
map
((
chunk
)
=>
decodeTextChunk
(
chunk
.
data
))
.
find
((
entry
)
=>
entry
.
keyword
===
textChunkKeyword
);
if
(
!
textChunk
)
{
throw
new
Error
(
`No PNG text chunk named "
${
textChunkKeyword
}
" found`
);
}
try
{
return
new
TextDecoder
().
decode
(
Uint8Array
.
from
(
atob
(
textChunk
.
text
),
(
c
)
=>
c
.
charCodeAt
(
0
)));
}
catch
(
e
)
{
throw
new
Error
(
'
Unable to parse "chara" field as base64
'
,
e
);
}
};
const
readPngChunks
=
(
data
)
=>
{
const
isValidPng
=
data
[
0
]
===
0x89
&&
data
[
1
]
===
0x50
&&
data
[
2
]
===
0x4e
&&
data
[
3
]
===
0x47
&&
data
[
4
]
===
0x0d
&&
data
[
5
]
===
0x0a
&&
data
[
6
]
===
0x1a
&&
data
[
7
]
===
0x0a
;
if
(
!
isValidPng
)
throw
new
Error
(
'
Invalid PNG file
'
);
let
chunks
=
[];
let
offset
=
8
;
// Skip PNG signature
while
(
offset
<
data
.
length
)
{
let
length
=
(
data
[
offset
]
<<
24
)
|
(
data
[
offset
+
1
]
<<
16
)
|
(
data
[
offset
+
2
]
<<
8
)
|
data
[
offset
+
3
];
let
type
=
String
.
fromCharCode
.
apply
(
null
,
data
.
slice
(
offset
+
4
,
offset
+
8
));
let
chunkData
=
data
.
slice
(
offset
+
8
,
offset
+
8
+
length
);
let
crc
=
(
data
[
offset
+
8
+
length
]
<<
24
)
|
(
data
[
offset
+
8
+
length
+
1
]
<<
16
)
|
(
data
[
offset
+
8
+
length
+
2
]
<<
8
)
|
data
[
offset
+
8
+
length
+
3
];
if
(
CRC32
.
buf
(
chunkData
,
CRC32
.
str
(
type
))
!==
crc
)
{
throw
new
Error
(
`Invalid CRC for chunk type "
${
type
}
"`
);
}
chunks
.
push
({
type
,
data
:
chunkData
,
crc
});
offset
+=
12
+
length
;
}
return
chunks
;
};
const
decodeTextChunk
=
(
data
)
=>
{
let
i
=
0
;
const
keyword
=
[];
const
text
=
[];
for
(;
i
<
data
.
length
&&
data
[
i
]
!==
0
;
i
++
)
{
keyword
.
push
(
String
.
fromCharCode
(
data
[
i
]));
}
for
(
i
++
;
i
<
data
.
length
;
i
++
)
{
text
.
push
(
String
.
fromCharCode
(
data
[
i
]));
}
return
{
keyword
:
keyword
.
join
(
''
),
text
:
text
.
join
(
''
)
};
};
const
extractCharacter
=
(
json
)
=>
{
function
getTrimmedValue
(
json
,
keys
)
{
return
keys
.
map
((
key
)
=>
json
[
key
]).
find
((
value
)
=>
value
&&
value
.
trim
());
}
const
name
=
getTrimmedValue
(
json
,
[
'
char_name
'
,
'
name
'
]);
const
summary
=
getTrimmedValue
(
json
,
[
'
personality
'
,
'
title
'
]);
const
personality
=
getTrimmedValue
(
json
,
[
'
char_persona
'
,
'
description
'
]);
const
scenario
=
getTrimmedValue
(
json
,
[
'
world_scenario
'
,
'
scenario
'
]);
const
greeting
=
getTrimmedValue
(
json
,
[
'
char_greeting
'
,
'
greeting
'
,
'
first_mes
'
]);
const
examples
=
getTrimmedValue
(
json
,
[
'
example_dialogue
'
,
'
mes_example
'
,
'
definition
'
]);
return
{
name
,
summary
,
personality
,
scenario
,
greeting
,
examples
};
};
const
detectFormats
=
(
json
)
=>
{
const
formats
=
[];
if
(
json
.
char_name
&&
json
.
char_persona
&&
json
.
world_scenario
&&
json
.
char_greeting
&&
json
.
example_dialogue
)
formats
.
push
(
'
Text Generation Character
'
);
if
(
json
.
name
&&
json
.
personality
&&
json
.
description
&&
json
.
scenario
&&
json
.
first_mes
&&
json
.
mes_example
)
formats
.
push
(
'
TavernAI Character
'
);
if
(
json
.
character
&&
json
.
character
.
name
&&
json
.
character
.
title
&&
json
.
character
.
description
&&
json
.
character
.
greeting
&&
json
.
character
.
definition
)
formats
.
push
(
'
CharacterAI Character
'
);
if
(
json
.
info
&&
json
.
info
.
character
&&
json
.
info
.
character
.
name
&&
json
.
info
.
character
.
title
&&
json
.
info
.
character
.
description
&&
json
.
info
.
character
.
greeting
)
formats
.
push
(
'
CharacterAI History
'
);
return
formats
;
};
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment