2023-10-19 17:14:48 +03:00
const colorThemes = document . querySelectorAll ( '[name="theme"]' ) ;
const markdown = window . markdownit ( ) ;
const message _box = document . getElementById ( ` messages ` ) ;
const message _input = document . getElementById ( ` message-input ` ) ;
2023-10-06 21:52:17 +03:00
const box _conversations = document . querySelector ( ` .top ` ) ;
2023-10-19 17:14:48 +03:00
const spinner = box _conversations . querySelector ( ".spinner" ) ;
const stop _generating = document . querySelector ( ` .stop_generating ` ) ;
2023-12-10 23:46:11 +03:00
const regenerate = document . querySelector ( ` .regenerate ` ) ;
2023-10-19 17:14:48 +03:00
const send _button = document . querySelector ( ` #send-button ` ) ;
let prompt _lock = false ;
2023-10-06 21:52:17 +03:00
hljs . addPlugin ( new CopyButtonPlugin ( ) ) ;
message _input . addEventListener ( "blur" , ( ) => {
window . scrollTo ( 0 , 0 ) ;
} ) ;
message _input . addEventListener ( "focus" , ( ) => {
document . documentElement . scrollTop = document . documentElement . scrollHeight ;
} ) ;
2023-12-10 23:46:11 +03:00
const markdown _render = ( content ) => {
return markdown . render ( content ) . replace ( "<a href=" , '<a target="_blank" href=' ) . replace ( '<code>' , '<code class="language-plaintext">' )
}
2023-10-06 21:52:17 +03:00
const delete _conversations = async ( ) => {
localStorage . clear ( ) ;
await new _conversation ( ) ;
} ;
const handle _ask = async ( ) => {
message _input . style . height = ` 80px ` ;
message _input . focus ( ) ;
window . scrollTo ( 0 , 0 ) ;
2023-12-10 23:46:11 +03:00
message = message _input . value
2023-10-06 21:52:17 +03:00
if ( message . length > 0 ) {
2023-12-10 23:46:11 +03:00
message _input . value = '' ;
await add _conversation ( window . conversation _id , message ) ;
await add _message ( window . conversation _id , "user" , message ) ;
window . token = message _id ( ) ;
message _box . innerHTML += `
< div class = "message" >
< div class = "user" >
$ { user _image }
< i class = "fa-regular fa-phone-arrow-up-right" > < / i >
< / d i v >
< div class = "content" id = "user_${token}" >
$ { markdown _render ( message ) }
< / d i v >
< / d i v >
` ;
await ask _gpt ( ) ;
2023-10-06 21:52:17 +03:00
}
} ;
const remove _cancel _button = async ( ) => {
stop _generating . classList . add ( ` stop_generating-hiding ` ) ;
setTimeout ( ( ) => {
stop _generating . classList . remove ( ` stop_generating-hiding ` ) ;
stop _generating . classList . add ( ` stop_generating-hidden ` ) ;
} , 300 ) ;
} ;
2023-12-10 23:46:11 +03:00
const ask _gpt = async ( ) => {
regenerate . classList . add ( ` regenerate-hidden ` ) ;
messages = await get _messages ( window . conversation _id ) ;
2023-10-06 21:52:17 +03:00
try {
window . scrollTo ( 0 , 0 ) ;
window . controller = new AbortController ( ) ;
2023-10-10 01:47:58 +03:00
jailbreak = document . getElementById ( "jailbreak" ) ;
provider = document . getElementById ( "provider" ) ;
model = document . getElementById ( "model" ) ;
prompt _lock = true ;
window . text = ` ` ;
2023-10-06 21:52:17 +03:00
stop _generating . classList . remove ( ` stop_generating-hidden ` ) ;
message _box . scrollTop = message _box . scrollHeight ;
window . scrollTo ( 0 , 0 ) ;
await new Promise ( ( r ) => setTimeout ( r , 500 ) ) ;
window . scrollTo ( 0 , 0 ) ;
message _box . innerHTML += `
< div class = "message" >
2023-10-27 23:35:10 +03:00
< div class = "assistant" >
2023-10-06 21:52:17 +03:00
$ { gpt _image } < i class = "fa-regular fa-phone-arrow-down-left" > < / i >
< / d i v >
< div class = "content" id = "gpt_${window.token}" >
< div id = "cursor" > < / d i v >
< / d i v >
< / d i v >
` ;
message _box . scrollTop = message _box . scrollHeight ;
window . scrollTo ( 0 , 0 ) ;
await new Promise ( ( r ) => setTimeout ( r , 1000 ) ) ;
window . scrollTo ( 0 , 0 ) ;
const response = await fetch ( ` /backend-api/v2/conversation ` , {
method : ` POST ` ,
signal : window . controller . signal ,
headers : {
2023-10-19 17:14:48 +03:00
'content-type' : ` application/json ` ,
2023-10-06 21:52:17 +03:00
accept : ` text/event-stream ` ,
} ,
body : JSON . stringify ( {
conversation _id : window . conversation _id ,
action : ` _ask ` ,
model : model . options [ model . selectedIndex ] . value ,
jailbreak : jailbreak . options [ jailbreak . selectedIndex ] . value ,
2023-12-10 23:46:11 +03:00
internet _access : document . getElementById ( ` switch ` ) . checked ,
2023-10-10 01:47:58 +03:00
provider : provider . options [ provider . selectedIndex ] . value ,
2023-10-06 21:52:17 +03:00
meta : {
id : window . token ,
content : {
2023-10-19 17:14:48 +03:00
content _type : ` text ` ,
2023-12-10 23:46:11 +03:00
parts : messages ,
2023-10-06 21:52:17 +03:00
} ,
} ,
} ) ,
} ) ;
const reader = response . body . getReader ( ) ;
while ( true ) {
const { value , done } = await reader . read ( ) ;
if ( done ) break ;
chunk = new TextDecoder ( ) . decode ( value ) ;
text += chunk ;
2023-12-10 23:46:11 +03:00
document . getElementById ( ` gpt_ ${ window . token } ` ) . innerHTML = markdown _render ( text ) ;
2023-10-06 21:52:17 +03:00
document . querySelectorAll ( ` code ` ) . forEach ( ( el ) => {
hljs . highlightElement ( el ) ;
} ) ;
window . scrollTo ( 0 , 0 ) ;
message _box . scrollTo ( { top : message _box . scrollHeight , behavior : "auto" } ) ;
}
2023-10-10 17:48:56 +03:00
if ( text . includes ( ` G4F_ERROR ` ) ) {
2023-12-10 23:46:11 +03:00
document . getElementById ( ` gpt_ ${ window . token } ` ) . innerHTML = "An error occured, please try again, if the problem persists, please use a other model or provider" ;
2023-10-06 21:52:17 +03:00
}
add _message ( window . conversation _id , "assistant" , text ) ;
message _box . scrollTop = message _box . scrollHeight ;
await remove _cancel _button ( ) ;
prompt _lock = false ;
await load _conversations ( 20 , 0 ) ;
window . scrollTo ( 0 , 0 ) ;
2023-10-19 17:14:48 +03:00
2023-10-06 21:52:17 +03:00
} catch ( e ) {
message _box . scrollTop = message _box . scrollHeight ;
await remove _cancel _button ( ) ;
prompt _lock = false ;
await load _conversations ( 20 , 0 ) ;
console . log ( e ) ;
let cursorDiv = document . getElementById ( ` cursor ` ) ;
if ( cursorDiv ) cursorDiv . parentNode . removeChild ( cursorDiv ) ;
if ( e . name != ` AbortError ` ) {
let error _message = ` oops ! something went wrong, please try again / reload. [stacktrace in console] ` ;
document . getElementById ( ` gpt_ ${ window . token } ` ) . innerHTML = error _message ;
add _message ( window . conversation _id , "assistant" , error _message ) ;
} else {
document . getElementById ( ` gpt_ ${ window . token } ` ) . innerHTML += ` [aborted] ` ;
add _message ( window . conversation _id , "assistant" , text + ` [aborted] ` ) ;
}
window . scrollTo ( 0 , 0 ) ;
}
2023-12-10 23:46:11 +03:00
regenerate . classList . remove ( ` regenerate-hidden ` ) ;
2023-10-06 21:52:17 +03:00
} ;
const clear _conversations = async ( ) => {
const elements = box _conversations . childNodes ;
let index = elements . length ;
if ( index > 0 ) {
while ( index -- ) {
const element = elements [ index ] ;
if (
element . nodeType === Node . ELEMENT _NODE &&
element . tagName . toLowerCase ( ) !== ` button `
) {
box _conversations . removeChild ( element ) ;
}
}
}
} ;
const clear _conversation = async ( ) => {
let messages = message _box . getElementsByTagName ( ` div ` ) ;
while ( messages . length > 0 ) {
message _box . removeChild ( messages [ 0 ] ) ;
}
} ;
const show _option = async ( conversation _id ) => {
const conv = document . getElementById ( ` conv- ${ conversation _id } ` ) ;
const yes = document . getElementById ( ` yes- ${ conversation _id } ` ) ;
const not = document . getElementById ( ` not- ${ conversation _id } ` ) ;
2023-10-19 17:14:48 +03:00
conv . style . display = ` none ` ;
yes . style . display = ` block ` ;
not . style . display = ` block ` ;
2023-10-06 21:52:17 +03:00
} ;
const hide _option = async ( conversation _id ) => {
const conv = document . getElementById ( ` conv- ${ conversation _id } ` ) ;
2023-10-19 17:14:48 +03:00
const yes = document . getElementById ( ` yes- ${ conversation _id } ` ) ;
const not = document . getElementById ( ` not- ${ conversation _id } ` ) ;
2023-10-06 21:52:17 +03:00
2023-10-19 17:14:48 +03:00
conv . style . display = ` block ` ;
yes . style . display = ` none ` ;
not . style . display = ` none ` ;
2023-10-06 21:52:17 +03:00
} ;
const delete _conversation = async ( conversation _id ) => {
localStorage . removeItem ( ` conversation: ${ conversation _id } ` ) ;
const conversation = document . getElementById ( ` convo- ${ conversation _id } ` ) ;
conversation . remove ( ) ;
if ( window . conversation _id == conversation _id ) {
await new _conversation ( ) ;
}
await load _conversations ( 20 , 0 , true ) ;
} ;
const set _conversation = async ( conversation _id ) => {
history . pushState ( { } , null , ` /chat/ ${ conversation _id } ` ) ;
window . conversation _id = conversation _id ;
await clear _conversation ( ) ;
await load _conversation ( conversation _id ) ;
await load _conversations ( 20 , 0 , true ) ;
} ;
const new _conversation = async ( ) => {
history . pushState ( { } , null , ` /chat/ ` ) ;
window . conversation _id = uuid ( ) ;
await clear _conversation ( ) ;
await load _conversations ( 20 , 0 , true ) ;
2023-10-19 17:14:48 +03:00
await say _hello ( )
2023-10-06 21:52:17 +03:00
} ;
const load _conversation = async ( conversation _id ) => {
2023-12-10 23:46:11 +03:00
let messages = await get _messages ( conversation _id ) ;
2023-10-06 21:52:17 +03:00
2023-12-10 23:46:11 +03:00
for ( item of messages ) {
2023-10-06 21:52:17 +03:00
message _box . innerHTML += `
< div class = "message" >
2023-10-27 23:35:10 +03:00
< div class = $ { item . role == "assistant" ? "assistant" : "user" } >
2023-10-06 21:52:17 +03:00
$ { item . role == "assistant" ? gpt _image : user _image }
$ { item . role == "assistant"
2023-10-19 17:14:48 +03:00
? ` <i class="fa-regular fa-phone-arrow-down-left"></i> `
: ` <i class="fa-regular fa-phone-arrow-up-right"></i> `
}
2023-10-06 21:52:17 +03:00
< / d i v >
< div class = "content" >
$ { item . role == "assistant"
2023-12-10 23:46:11 +03:00
? markdown _render ( item . content )
2023-10-19 17:14:48 +03:00
: item . content
}
2023-10-06 21:52:17 +03:00
< / d i v >
< / d i v >
` ;
}
document . querySelectorAll ( ` code ` ) . forEach ( ( el ) => {
hljs . highlightElement ( el ) ;
} ) ;
message _box . scrollTo ( { top : message _box . scrollHeight , behavior : "smooth" } ) ;
setTimeout ( ( ) => {
message _box . scrollTop = message _box . scrollHeight ;
} , 500 ) ;
} ;
const get _conversation = async ( conversation _id ) => {
let conversation = await JSON . parse (
localStorage . getItem ( ` conversation: ${ conversation _id } ` )
) ;
2023-12-10 23:46:11 +03:00
return conversation ;
} ;
const get _messages = async ( conversation _id ) => {
let conversation = await get _conversation ( conversation _id ) ;
2023-10-06 21:52:17 +03:00
return conversation . items ;
} ;
const add _conversation = async ( conversation _id , content ) => {
if ( content . length > 17 ) {
2023-10-27 23:35:10 +03:00
title = content . substring ( 0 , 17 ) + '...'
2023-10-06 21:52:17 +03:00
} else {
title = content + ' ' . repeat ( 19 - content . length )
}
if ( localStorage . getItem ( ` conversation: ${ conversation _id } ` ) == null ) {
localStorage . setItem (
` conversation: ${ conversation _id } ` ,
JSON . stringify ( {
id : conversation _id ,
title : title ,
items : [ ] ,
} )
) ;
}
2023-12-10 23:46:11 +03:00
history . pushState ( { } , null , ` /chat/ ${ conversation _id } ` ) ;
2023-10-06 21:52:17 +03:00
} ;
2023-12-10 23:46:11 +03:00
const remove _last _message = async ( conversation _id ) => {
const conversation = await get _conversation ( conversation _id )
conversation . items . pop ( ) ;
localStorage . setItem (
` conversation: ${ conversation _id } ` ,
JSON . stringify ( conversation )
2023-10-06 21:52:17 +03:00
) ;
2023-12-10 23:46:11 +03:00
} ;
const add _message = async ( conversation _id , role , content ) => {
const conversation = await get _conversation ( conversation _id ) ;
2023-10-06 21:52:17 +03:00
2023-12-10 23:46:11 +03:00
conversation . items . push ( {
2023-10-06 21:52:17 +03:00
role : role ,
content : content ,
} ) ;
localStorage . setItem (
` conversation: ${ conversation _id } ` ,
2023-12-10 23:46:11 +03:00
JSON . stringify ( conversation )
2023-10-19 17:14:48 +03:00
) ;
2023-10-06 21:52:17 +03:00
} ;
const load _conversations = async ( limit , offset , loader ) => {
let conversations = [ ] ;
for ( let i = 0 ; i < localStorage . length ; i ++ ) {
if ( localStorage . key ( i ) . startsWith ( "conversation:" ) ) {
let conversation = localStorage . getItem ( localStorage . key ( i ) ) ;
conversations . push ( JSON . parse ( conversation ) ) ;
}
}
await clear _conversations ( ) ;
for ( conversation of conversations ) {
box _conversations . innerHTML += `
< div class = "convo" id = "convo-${conversation.id}" >
< div class = "left" onclick = "set_conversation('${conversation.id}')" >
< i class = "fa-regular fa-comments" > < / i >
< span class = "convo-title" > $ { conversation . title } < / s p a n >
< / d i v >
< i onclick = "show_option('${conversation.id}')" class = "fa-regular fa-trash" id = "conv-${conversation.id}" > < / i >
< i onclick = "delete_conversation('${conversation.id}')" class = "fa-regular fa-check" id = "yes-${conversation.id}" style = "display:none;" > < / i >
< i onclick = "hide_option('${conversation.id}')" class = "fa-regular fa-x" id = "not-${conversation.id}" style = "display:none;" > < / i >
< / d i v >
` ;
}
document . querySelectorAll ( ` code ` ) . forEach ( ( el ) => {
hljs . highlightElement ( el ) ;
} ) ;
} ;
document . getElementById ( ` cancelButton ` ) . addEventListener ( ` click ` , async ( ) => {
window . controller . abort ( ) ;
console . log ( ` aborted ${ window . conversation _id } ` ) ;
} ) ;
2023-12-10 23:46:11 +03:00
document . getElementById ( ` regenerateButton ` ) . addEventListener ( ` click ` , async ( ) => {
await remove _last _message ( window . conversation _id ) ;
window . token = message _id ( ) ;
await ask _gpt ( ) ;
} ) ;
2023-10-06 21:52:17 +03:00
const uuid = ( ) => {
return ` xxxxxxxx-xxxx-4xxx-yxxx- ${ Date . now ( ) . toString ( 16 ) } ` . replace (
/[xy]/g ,
function ( c ) {
var r = ( Math . random ( ) * 16 ) | 0 ,
v = c == "x" ? r : ( r & 0x3 ) | 0x8 ;
return v . toString ( 16 ) ;
}
) ;
} ;
const message _id = ( ) => {
random _bytes = ( Math . floor ( Math . random ( ) * 1338377565 ) + 2956589730 ) . toString (
2
) ;
unix = Math . floor ( Date . now ( ) / 1000 ) . toString ( 2 ) ;
return BigInt ( ` 0b ${ unix } ${ random _bytes } ` ) . toString ( ) ;
} ;
document . querySelector ( ".mobile-sidebar" ) . addEventListener ( "click" , ( event ) => {
const sidebar = document . querySelector ( ".conversations" ) ;
if ( sidebar . classList . contains ( "shown" ) ) {
sidebar . classList . remove ( "shown" ) ;
event . target . classList . remove ( "rotated" ) ;
} else {
sidebar . classList . add ( "shown" ) ;
event . target . classList . add ( "rotated" ) ;
}
window . scrollTo ( 0 , 0 ) ;
} ) ;
const register _settings _localstorage = async ( ) => {
settings _ids = [ "switch" , "model" , "jailbreak" ] ;
settings _elements = settings _ids . map ( ( id ) => document . getElementById ( id ) ) ;
settings _elements . map ( ( element ) =>
element . addEventListener ( ` change ` , async ( event ) => {
switch ( event . target . type ) {
case "checkbox" :
localStorage . setItem ( event . target . id , event . target . checked ) ;
break ;
case "select-one" :
localStorage . setItem ( event . target . id , event . target . selectedIndex ) ;
break ;
default :
console . warn ( "Unresolved element type" ) ;
}
} )
) ;
} ;
const load _settings _localstorage = async ( ) => {
settings _ids = [ "switch" , "model" , "jailbreak" ] ;
settings _elements = settings _ids . map ( ( id ) => document . getElementById ( id ) ) ;
settings _elements . map ( ( element ) => {
if ( localStorage . getItem ( element . id ) ) {
switch ( element . type ) {
case "checkbox" :
element . checked = localStorage . getItem ( element . id ) === "true" ;
break ;
case "select-one" :
element . selectedIndex = parseInt ( localStorage . getItem ( element . id ) ) ;
break ;
default :
console . warn ( "Unresolved element type" ) ;
}
}
} ) ;
} ;
2023-10-19 17:14:48 +03:00
const say _hello = async ( ) => {
2023-10-06 21:52:17 +03:00
tokens = [ ` Hello ` , ` ! ` , ` How ` , ` can ` , ` I ` , ` assist ` , ` you ` , ` today ` , ` ? ` ]
message _box . innerHTML += `
< div class = "message" >
2023-10-27 23:35:10 +03:00
< div class = "assistant" >
2023-10-06 21:52:17 +03:00
$ { gpt _image }
< i class = "fa-regular fa-phone-arrow-down-left" > < / i >
< / d i v >
2023-12-10 23:46:11 +03:00
< div class = "content" >
< p class = " welcome-message" > < / p >
2023-10-06 21:52:17 +03:00
< / d i v >
< / d i v >
` ;
to _modify = document . querySelector ( ` .welcome-message ` ) ;
for ( token of tokens ) {
await new Promise ( resolve => setTimeout ( resolve , ( Math . random ( ) * ( 100 - 200 ) + 100 ) ) )
2023-12-10 23:46:11 +03:00
to _modify . textContent += token ;
2023-10-06 21:52:17 +03:00
}
}
// Theme storage for recurring viewers
const storeTheme = function ( theme ) {
localStorage . setItem ( "theme" , theme ) ;
} ;
// set theme when visitor returns
const setTheme = function ( ) {
const activeTheme = localStorage . getItem ( "theme" ) ;
colorThemes . forEach ( ( themeOption ) => {
if ( themeOption . id === activeTheme ) {
themeOption . checked = true ;
}
} ) ;
// fallback for no :has() support
document . documentElement . className = activeTheme ;
} ;
colorThemes . forEach ( ( themeOption ) => {
themeOption . addEventListener ( "click" , ( ) => {
storeTheme ( themeOption . id ) ;
// fallback for no :has() support
document . documentElement . className = themeOption . id ;
} ) ;
} ) ;
window . onload = async ( ) => {
load _settings _localstorage ( ) ;
setTheme ( ) ;
2023-12-06 11:35:36 +03:00
let conversations = 0 ;
2023-10-06 21:52:17 +03:00
for ( let i = 0 ; i < localStorage . length ; i ++ ) {
if ( localStorage . key ( i ) . startsWith ( "conversation:" ) ) {
conversations += 1 ;
}
}
if ( conversations == 0 ) localStorage . clear ( ) ;
await setTimeout ( ( ) => {
load _conversations ( 20 , 0 ) ;
} , 1 ) ;
2023-12-10 23:46:11 +03:00
if ( /\/chat\/.+/ . test ( window . location . href ) ) {
await load _conversation ( window . conversation _id ) ;
} else {
await say _hello ( )
2023-10-06 21:52:17 +03:00
}
2023-12-10 23:46:11 +03:00
2023-10-06 21:52:17 +03:00
message _input . addEventListener ( ` keydown ` , async ( evt ) => {
if ( prompt _lock ) return ;
if ( evt . keyCode === 13 && ! evt . shiftKey ) {
evt . preventDefault ( ) ;
console . log ( "pressed enter" ) ;
await handle _ask ( ) ;
} else {
message _input . style . removeProperty ( "height" ) ;
message _input . style . height = message _input . scrollHeight + "px" ;
}
} ) ;
send _button . addEventListener ( ` click ` , async ( ) => {
console . log ( "clicked send" ) ;
if ( prompt _lock ) return ;
await handle _ask ( ) ;
} ) ;
register _settings _localstorage ( ) ;
} ;
const observer = new MutationObserver ( ( mutationsList ) => {
for ( const mutation of mutationsList ) {
if ( mutation . type === 'attributes' && mutation . attributeName === 'style' ) {
const height = message _input . offsetHeight ;
let heightValues = {
81 : "20px" ,
82 : "20px" ,
100 : "30px" ,
119 : "39px" ,
138 : "49px" ,
150 : "55px"
}
send _button . style . top = heightValues [ height ] || '' ;
}
}
} ) ;
2023-10-19 17:14:48 +03:00
observer . observe ( message _input , { attributes : true } ) ;
2023-12-06 11:35:36 +03:00
( async ( ) => {
response = await fetch ( '/backend-api/v2/models' )
models = await response . json ( )
2023-10-19 22:25:13 +03:00
2023-12-06 11:35:36 +03:00
let select = document . getElementById ( 'model' ) ;
select . textContent = '' ;
let auto = document . createElement ( 'option' ) ;
auto . value = '' ;
2023-12-06 13:54:50 +03:00
auto . text = 'Model: Default' ;
2023-12-06 11:35:36 +03:00
select . appendChild ( auto ) ;
2023-10-19 17:14:48 +03:00
2023-10-19 21:37:56 +03:00
for ( model of models ) {
2023-12-06 11:35:36 +03:00
let option = document . createElement ( 'option' ) ;
option . value = option . text = model ;
select . appendChild ( option ) ;
}
} ) ( ) ;
2023-10-19 17:14:48 +03:00
2023-12-06 11:35:36 +03:00
( async ( ) => {
response = await fetch ( '/backend-api/v2/providers' )
providers = await response . json ( )
let select = document . getElementById ( 'provider' ) ;
select . textContent = '' ;
let auto = document . createElement ( 'option' ) ;
auto . value = '' ;
auto . text = 'Provider: Auto' ;
select . appendChild ( auto ) ;
for ( provider of providers ) {
let option = document . createElement ( 'option' ) ;
option . value = option . text = provider ;
select . appendChild ( option ) ;
2023-10-19 17:14:48 +03:00
}
2023-12-07 09:18:05 +03:00
} ) ( ) ;
( async ( ) => {
response = await fetch ( '/backend-api/v2/version' )
versions = await response . json ( )
document . title = 'g4f - gui - ' + versions [ "version" ] ;
text = "version ~ "
if ( versions [ "version" ] != versions [ "lastet_version" ] ) {
release _url = 'https://github.com/xtekky/gpt4free/releases/tag/' + versions [ "lastet_version" ] ;
text += '<a href="' + release _url + '" target="_blank" title="New version: ' + versions [ "lastet_version" ] + '">' + versions [ "version" ] + ' 🆕</a>' ;
} else {
text += versions [ "version" ] ;
}
document . getElementById ( "version_text" ) . innerHTML = text
2023-12-06 11:35:36 +03:00
} ) ( ) ;