Upgraded UI

This commit is contained in:
Saifeddine ALOUI 2024-10-19 01:54:04 +02:00
parent 5acbf0d111
commit ffe2066ef1
9 changed files with 1359 additions and 1289 deletions

@ -1 +1 @@
Subproject commit b1564948940e56ac3cec8a769b0375324224d041
Subproject commit 7424d7a960212ff94bc7564bc25a397f7e34599c

File diff suppressed because one or more lines are too long

8
web/dist/assets/index-BnGFpX1y.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
web/dist/index.html vendored
View File

@ -6,8 +6,8 @@
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LoLLMS WebUI</title>
<script type="module" crossorigin src="/assets/index-BzEWObzC.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Bo-yP1OD.css">
<script type="module" crossorigin src="/assets/index-BjLC6vKM.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BnGFpX1y.css">
</head>
<body>
<div id="app"></div>

View File

@ -76,7 +76,7 @@ textarea, input, select {
}
.panels-color {
@apply text-blue-700 dark:text-blue-200 bg-blue-100 dark:bg-blue-800 rounded-lg shadow-md;
@apply text-blue-700 dark:text-blue-200 bg-blue-100 dark:bg-blue-800;
}
.unicolor-panels-color {
@ -117,7 +117,7 @@ body {
}
.discussion {
@apply mr-2 bg-blue-100 dark:bg-blue-900 text-xs;
@apply mr-2 text-xs;
}
.discussion-hilighted {

View File

@ -71,9 +71,7 @@
</div>
</div>
<div v-if="filesList.length > 0" class="flex mx-1 w-500">
<!-- ADDITIONAL INFO PANEL -->
<div class="whitespace-nowrap flex flex-row gap-2">
<p class="font-bold ">
Total size:
@ -110,383 +108,382 @@
</div>
</div>
<!-- CHAT BOX -->
<div v-if="selecting_model||selecting_binding" title="Selecting model" class="flex flex-row flex-grow justify-end panels-color">
<!-- CHAT BOX -->
<div v-if="selecting_model||selecting_binding" title="Selecting model" class="flex flex-row flex-grow justify-end panels-color">
<!-- SPINNER -->
<div role="status">
<img :src="loader_v0" class="w-50 h-50">
<span class="sr-only">Selecting model...</span>
</div>
</div>
<div class="flex w-fit relative grow w-full">
<div class="relative text-light-text-panel dark:text-dark-text-panel grow flex h-12.5 cursor-pointer select-none items-center gap-2 chatbox-color p-1 shadow-sm hover:shadow-none dark:border-gray-800" tabindex="0">
<div v-if="loading" title="Waiting for reply">
<img :src="loader_v0">
<!-- SPINNER -->
<div role="status">
<img :src="loader_v0" class="w-50 h-50">
<span class="sr-only">Selecting model...</span>
<span class="sr-only">Loading...</span>
</div>
</div>
<div class="flex w-fit relative grow w-full">
<div class="relative text-light-text-panel dark:text-dark-text-panel grow flex h-12.5 cursor-pointer select-none items-center gap-2 chatbox-color p-1 shadow-sm hover:shadow-none dark:border-gray-800" tabindex="0">
<div v-if="loading" title="Waiting for reply">
<img :src="loader_v0">
<!-- SPINNER -->
<div role="status">
<span class="sr-only">Loading...</span>
<ChatBarButton
@click="toggleLeftPanel"
:class="{ 'text-red-500': leftPanelCollapsed }"
title="Toggle View Mode"
>
<div v-show="leftPanelCollapsed">
<!-- Chevron Right SVG -->
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</div>
<div v-show="!leftPanelCollapsed">
<!-- Chevron Left SVG -->
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="15 18 9 12 15 6"></polyline>
</svg>
</div>
</ChatBarButton>
<ChatBarButton
@click="toggleViewMode"
:class="{ 'text-red-500': isCompactMode }"
title="Toggle View Mode"
>
<template #icon>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
v-if="isCompactMode"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 12h18M3 6h18M3 18h18"
/>
<path
v-else
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 12h18M3 6h18M3 18h18M12 6v12"
/>
</svg>
</template>
</ChatBarButton>
<div class="w-fit group relative" v-if="!loading" >
<div class= "hide top-50 hide opacity-0 group-hover:bottom-0 opacity-0 .group-hover:block fixed w-[1000px] group absolute group-hover:opacity-100 transform group-hover:translate-y-[-50px] group-hover:translate-x-[0px] transition-all duration-300">
<div class="w-fit flex-wrap flex bg-white bg-opacity-50 backdrop-blur-md rounded p-4">
<div class="w-fit h-fit inset-0 opacity-100"
v-for="(item, index) in installedBindings" :key="index + '-' + item.name"
ref="installedBindings"
@mouseover="showBindingHoveredIn(index)" @mouseleave="showBindingHoveredOut()"
>
<div v-if="index!=binding_name" class="items-center flex flex-row relative z-20 hover:-translate-y-8 duration-300"
:class="bindingHoveredIndex === index?'scale-150':''"
>
<div class="relative">
<button @click.prevent="setBinding(item)" class="w-10 h-10 relative">
<img :src="item.icon?item.icon:modelImgPlaceholder" @error="modelImgPlaceholder"
class="z-50 w-10 h-10 rounded-full object-fill text-red-700 border-2 border-gray-500 active:scale-90"
:class="bindingHoveredIndex === index?'scale-150 ':'' + item.name==binding_name ? 'border-secondary' : 'border-transparent z-0'"
:title="item.name">
</button>
</div>
</div>
</div>
</div>
</div>
<div class="group items-center flex flex-row">
<button @click.prevent="showModelConfig()" class="w-8 h-8">
<img :src="currentBindingIcon"
class="w-8 h-8 rounded-full object-fill text-red-700 border-2 active:scale-90 hover:border-secondary hover:scale-110 hover:-translate-y-1 duration-200"
:title="currentBinding?currentBinding.name:'unknown'">
</button>
</div>
<ChatBarButton
@click="toggleLeftPanel"
:class="{ 'text-red-500': leftPanelCollapsed }"
title="Toggle View Mode"
>
<div v-show="leftPanelCollapsed">
<!-- Chevron Right SVG -->
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
</div>
<div class="w-fit group relative" v-if="!loading">
<div class="hide top-50 hide opacity-0 group-hover:bottom-0 opacity-0 .group-hover:block fixed w-[1000px] group absolute group-hover:opacity-100 transform group-hover:translate-y-[-50px] group-hover:translate-x-[0px] transition-all duration-300">
<div class="w-fit flex-wrap flex bg-white bg-opacity-50 backdrop-blur-md rounded p-4">
<div class="w-fit h-fit"
v-for="(item, index) in installedModels" :key="index + '-' + item.name"
ref="installedModels"
@mouseover="showModelHoveredIn(index)"
@mouseleave="showModelHoveredOut()"
>
<div v-if="index!=model_name" class="items-center flex flex-row relative z-20 hover:-translate-y-8 duration-300"
:class="modelHoveredIndex === index ? 'scale-150' : ''"
>
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</div>
<div v-show="!leftPanelCollapsed">
<!-- Chevron Left SVG -->
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="15 18 9 12 15 6"></polyline>
</svg>
</div>
</ChatBarButton>
<ChatBarButton
@click="toggleViewMode"
:class="{ 'text-red-500': isCompactMode }"
title="Toggle View Mode"
>
<template #icon>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
v-if="isCompactMode"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 12h18M3 6h18M3 18h18"
/>
<path
v-else
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 12h18M3 6h18M3 18h18M12 6v12"
/>
</svg>
</template>
</ChatBarButton>
<div class="w-fit group relative" v-if="!loading" >
<div class= "hide top-50 hide opacity-0 group-hover:bottom-0 opacity-0 .group-hover:block fixed w-[1000px] group absolute group-hover:opacity-100 transform group-hover:translate-y-[-50px] group-hover:translate-x-[0px] transition-all duration-300">
<div class="w-fit flex-wrap flex bg-white bg-opacity-50 backdrop-blur-md rounded p-4">
<div class="w-fit h-fit inset-0 opacity-100"
v-for="(item, index) in installedBindings" :key="index + '-' + item.name"
ref="installedBindings"
@mouseover="showBindingHoveredIn(index)" @mouseleave="showBindingHoveredOut()"
>
<div v-if="index!=binding_name" class="items-center flex flex-row relative z-20 hover:-translate-y-8 duration-300"
:class="bindingHoveredIndex === index?'scale-150':''"
>
<div class="relative">
<button @click.prevent="setBinding(item)" class="w-10 h-10 relative">
<img :src="item.icon?item.icon:modelImgPlaceholder" @error="modelImgPlaceholder"
class="z-50 w-10 h-10 rounded-full object-fill text-red-700 border-2 border-gray-500 active:scale-90"
:class="bindingHoveredIndex === index?'scale-150 ':'' + item.name==binding_name ? 'border-secondary' : 'border-transparent z-0'"
:title="item.name">
<div class="relative flex items-center">
<!-- Parent container for both buttons -->
<div class="relative group">
<button @click.prevent="setModel(item)" class="w-10 h-10 relative">
<img :src="item.icon ? item.icon : modelImgPlaceholder" @error="personalityImgPlacehodler"
class="z-50 w-10 h-10 rounded-full object-fill text-red-700 border-2 border-gray-500 active:scale-90"
:class="modelHoveredIndex === index ? 'scale-150' : '' + item.name == model_name ? 'border-secondary' : 'border-transparent z-0'"
:title="item.name">
</button>
<!-- New copy button with SVG icon that appears on hover -->
<button v-if="modelHoveredIndex === index" @click.prevent="copyModelNameFrom(item.name)"
class="absolute -top-2 -right-2 bg-blue-500 text-white p-1 rounded-full hover:bg-blue-700 transition duration-300">
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" viewBox="0 0 20 20" fill="currentColor">
<path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />
<path d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z" />
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="group items-center flex flex-row">
<button @click.prevent="showModelConfig()" class="w-8 h-8">
<img :src="currentBindingIcon"
class="w-8 h-8 rounded-full object-fill text-red-700 border-2 active:scale-90 hover:border-secondary hover:scale-110 hover:-translate-y-1 duration-200"
:title="currentBinding?currentBinding.name:'unknown'">
</button>
</div>
</div>
<div class="w-fit group relative" v-if="!loading">
<div class="hide top-50 hide opacity-0 group-hover:bottom-0 opacity-0 .group-hover:block fixed w-[1000px] group absolute group-hover:opacity-100 transform group-hover:translate-y-[-50px] group-hover:translate-x-[0px] transition-all duration-300">
<div class="w-fit flex-wrap flex bg-white bg-opacity-50 backdrop-blur-md rounded p-4">
<div class="w-fit h-fit"
v-for="(item, index) in installedModels" :key="index + '-' + item.name"
ref="installedModels"
@mouseover="showModelHoveredIn(index)"
@mouseleave="showModelHoveredOut()"
>
<div v-if="index!=model_name" class="items-center flex flex-row relative z-20 hover:-translate-y-8 duration-300"
:class="modelHoveredIndex === index ? 'scale-150' : ''"
>
<div class="relative flex items-center">
<!-- Parent container for both buttons -->
<div class="relative group">
<button @click.prevent="setModel(item)" class="w-10 h-10 relative">
<img :src="item.icon ? item.icon : modelImgPlaceholder" @error="personalityImgPlacehodler"
class="z-50 w-10 h-10 rounded-full object-fill text-red-700 border-2 border-gray-500 active:scale-90"
:class="modelHoveredIndex === index ? 'scale-150' : '' + item.name == model_name ? 'border-secondary' : 'border-transparent z-0'"
:title="item.name">
</button>
<!-- New copy button with SVG icon that appears on hover -->
<button v-if="modelHoveredIndex === index" @click.prevent="copyModelNameFrom(item.name)"
class="absolute -top-2 -right-2 bg-blue-500 text-white p-1 rounded-full hover:bg-blue-700 transition duration-300">
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" viewBox="0 0 20 20" fill="currentColor">
<path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />
<path d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z" />
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="group items-center flex flex-row">
<button @click.prevent="copyModelName()" class="w-8 h-8">
<img :src="currentModelIcon"
class="w-8 h-8 rounded-full object-fill text-red-700 border-2 active:scale-90 hover:border-secondary hover:scale-110 hover:-translate-y-1 duration-400"
:title="currentModel ? currentModel.name : 'unknown'">
</button>
</div>
</div>
<div class="w-fit group relative" v-if="!loading">
<!-- :onShowPersList="onShowPersListFun" -->
<div class= "top-50 hide opacity-0 group-hover:bottom-0 .group-hover:block fixed w-[1000px] group absolute group-hover:opacity-100 transform group-hover:translate-y-[-50px] group-hover:translate-x-[0px] transition-all duration-300">
<div class="w-fit flex-wrap flex bg-white bg-opacity-50 backdrop-blur-md rounded p-4">
<div class="w-fit h-fit inset-0 opacity-100"
v-for="(item, index) in mountedPersonalities" :key="index + '-' + item.name"
ref="mountedPersonalities"
@mouseover="showPersonalityHoveredIn(index)" @mouseleave="showPersonalityHoveredOut()"
>
<div v-if="index!=personality_name" class="items-center flex flex-row relative z-20 hover:-translate-y-8 duration-300"
:class="personalityHoveredIndex === index?'scale-150':''"
>
<div class="relative">
<button @click.prevent="onPersonalitySelected(item)" class="w-10 h-10 relative">
<img :src="bUrl + item.avatar" @error="personalityImgPlacehodler"
class="z-50 w-10 h-10 rounded-full object-fill text-red-700 border-2 border-gray-500 active:scale-90"
:class="personalityHoveredIndex === index?'scale-150 ':'' + this.$store.state.active_personality_id == this.$store.state.personalities.indexOf(item.full_path) ? 'border-secondary' : 'border-transparent z-0'"
:title="item.name">
</button>
<button @click.prevent="unmountPersonality(item)" v-if="personalityHoveredIndex === index" >
<span
class="-top-6 -right-6 border-gray-500 absolute active:scale-90 w-7 h-7 hover:scale-150 transition bg-bg-light dark:bg-bg-dark rounded-full border-2"
title="Unmount personality">
<!-- UNMOUNT BUTTON -->
<svg aria-hidden="true" class="top-1 left-1 relative w-5 h-5 text-red-600 hover:text-red-500 "
fill="currentColor" viewBox="0 0 20 20" stroke-width="1"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd"></path>
</svg>
</span>
</button>
<button @click.prevent="remount_personality(item)" v-if="personalityHoveredIndex === index">
<span
class="-top-9 left-2 border-gray-500 active:scale-90 absolute items-center w-7 h-7 hover:scale-150 transition text-red-200 absolute active:scale-90 bg-bg-light dark:bg-bg-dark rounded-full border-2"
title="Remount">
<!-- UNMOUNT BUTTON -->
<svg xmlns="http://www.w3.org/2000/svg" class="top-1 left-1 relative w-4 h-4 text-red-600 hover:text-red-500 " viewBox="0 0 30 30" width="2" height="2" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
<g id="surface1">
<path style=" " d="M 16 4 C 10.886719 4 6.617188 7.160156 4.875 11.625 L 6.71875 12.375 C 8.175781 8.640625 11.710938 6 16 6 C 19.242188 6 22.132813 7.589844 23.9375 10 L 20 10 L 20 12 L 27 12 L 27 5 L 25 5 L 25 8.09375 C 22.808594 5.582031 19.570313 4 16 4 Z M 25.28125 19.625 C 23.824219 23.359375 20.289063 26 16 26 C 12.722656 26 9.84375 24.386719 8.03125 22 L 12 22 L 12 20 L 5 20 L 5 27 L 7 27 L 7 23.90625 C 9.1875 26.386719 12.394531 28 16 28 C 21.113281 28 25.382813 24.839844 27.125 20.375 Z "/>
</g>
</svg>
</span>
</button>
<button @click.prevent="handleOnTalk(item)" v-if="personalityHoveredIndex === index">
<span
class="-top-6 -left-6 border-gray-500 active:scale-90 absolute items-center w-7 h-7 hover:scale-150 transition text-red-200 absolute active:scale-90 bg-bg-light dark:bg-bg-dark rounded-full border-2"
title="Talk">
<!-- UNMOUNT BUTTON -->
<svg xmlns="http://www.w3.org/2000/svg" class="top-1 left-1 relative w-4 h-4 text-red-600 hover:text-red-500 " viewBox="0 0 24 24" width="2" height="2" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
<line x1="22" y1="2" x2="11" y2="13"></line>
<polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
</svg>
</span>
</button>
</div>
</div>
</div>
</div>
</div>
<MountedPersonalities ref="mountedPers" :onShowPersList="onShowPersListFun" :onReady="onPersonalitiesReadyFun"/>
</div>
<div class="w-fit">
<PersonalitiesCommands
v-if="personalities_ready && this.$store.state.mountedPersArr[this.$store.state.config.active_personality_id].commands!=''"
:commandsList="this.$store.state.mountedPersArr[this.$store.state.config.active_personality_id].commands"
:sendCommand="sendCMDEvent"
:on-show-toast-message="onShowToastMessage"
ref="personalityCMD"
></PersonalitiesCommands>
</div>
<div class="w-fit">
<PersonalitiesCommands
v-if="isDataSourceNamesValid"
:icon="'feather:book'"
:commandsList="dataSourceNames"
:sendCommand="mountDB"
:on-show-toast-message="onShowToastMessage"
ref="databasesList"
></PersonalitiesCommands>
</div>
<div class="relative grow">
<form>
<textarea
id="chat"
rows="1"
v-model="message"
@paste="handlePaste"
@keydown.enter.exact="submitOnEnter($event)"
class="w-full p-3 text-sm text-gray-900 dark:text-white bg-gray-100 dark:bg-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
placeholder="Send message..."
></textarea>
</form>
</div>
<div class="flex items-center space-x-3">
<ChatBarButton
v-if="loading"
@click="stopGenerating"
class="bg-red-500 dark:bg-red-600 hover:bg-red-600 dark:hover:bg-red-700"
>
<template #icon>
<svg class="animate-spin h-5 w-5" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</template>
<span>Stop</span>
</ChatBarButton>
<ChatBarButton v-else @click="submit" title="Send">
<template #icon>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"/>
</svg>
</template>
</ChatBarButton>
<ChatBarButton @click="submitWithInternetSearch" title="Send with internet search">
<template #icon>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"/>
</svg>
</template>
</ChatBarButton>
<ChatBarButton
@click="startSpeechRecognition"
:class="{ 'text-red-500': isListeningToVoice }"
title="Voice input"
>
<template #icon>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z"/>
</svg>
</template>
</ChatBarButton>
<ChatBarButton
v-if="$store.state.config.active_tts_service != 'None' && $store.state.config.active_tts_service != null && this.$store.state.config.active_stt_service!='None' && this.$store.state.config.active_stt_service!=null"
@click="is_rt ? stopRTCom : startRTCom"
:class="is_rt ? 'bg-red-500 dark:bg-red-600' : 'bg-green-500 dark:bg-green-600'"
title="Real-time audio mode"
>
<template #icon>
🪶
</template>
</ChatBarButton>
<ChatBarButton @click="add_file" title="Send file">
<template #icon>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</template>
</ChatBarButton>
<ChatBarButton @click="takePicture" title="Take picture">
<template #icon>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
</template>
</ChatBarButton>
<ChatBarButton @click="addWebLink" title="Add web link">
<template #icon>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/>
</svg>
</template>
</ChatBarButton>
<ChatBarButton @click="makeAnEmptyUserMessage" title="New user message" class="text-gray-600 dark:text-gray-300">
<template #icon>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/>
</svg>
</template>
</ChatBarButton>
<ChatBarButton @click="makeAnEmptyAIMessage" title="New AI message" class="text-red-400 dark:text-red-300">
<template #icon>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
</svg>
</template>
</ChatBarButton>
<ChatBarButton
@click="toggleRightPanel"
:class="{ 'text-red-500': !rightPanelCollapsed }"
title="Toggle View Mode"
>
<template #icon>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
<circle cx="12" cy="5" r="2"></circle>
<path d="M12 7v4"></path>
<line x1="8" y1="16" x2="8" y2="16"></line>
<line x1="16" y1="16" x2="16" y2="16"></line>
</svg> </template>
</ChatBarButton>
</div>
<input type="file" ref="fileDialog" @change="addFiles" multiple style="display: none" />
</div>
<div class="ml-auto gap-2">
</div>
<div class="group items-center flex flex-row">
<button @click.prevent="copyModelName()" class="w-8 h-8">
<img :src="currentModelIcon"
class="w-8 h-8 rounded-full object-fill text-red-700 border-2 active:scale-90 hover:border-secondary hover:scale-110 hover:-translate-y-1 duration-400"
:title="currentModel ? currentModel.name : 'unknown'">
</button>
</div>
</div>
<div class="w-fit group relative" v-if="!loading">
<!-- :onShowPersList="onShowPersListFun" -->
<div class= "top-50 hide opacity-0 group-hover:bottom-0 .group-hover:block fixed w-[1000px] group absolute group-hover:opacity-100 transform group-hover:translate-y-[-50px] group-hover:translate-x-[0px] transition-all duration-300">
<div class="w-fit flex-wrap flex bg-white bg-opacity-50 backdrop-blur-md rounded p-4">
<div class="w-fit h-fit inset-0 opacity-100"
v-for="(item, index) in mountedPersonalities" :key="index + '-' + item.name"
ref="mountedPersonalities"
@mouseover="showPersonalityHoveredIn(index)" @mouseleave="showPersonalityHoveredOut()"
>
<div v-if="index!=personality_name" class="items-center flex flex-row relative z-20 hover:-translate-y-8 duration-300"
:class="personalityHoveredIndex === index?'scale-150':''"
>
<div class="relative">
<button @click.prevent="onPersonalitySelected(item)" class="w-10 h-10 relative">
<img :src="bUrl + item.avatar" @error="personalityImgPlacehodler"
class="z-50 w-10 h-10 rounded-full object-fill text-red-700 border-2 border-gray-500 active:scale-90"
:class="personalityHoveredIndex === index?'scale-150 ':'' + this.$store.state.active_personality_id == this.$store.state.personalities.indexOf(item.full_path) ? 'border-secondary' : 'border-transparent z-0'"
:title="item.name">
</button>
<button @click.prevent="unmountPersonality(item)" v-if="personalityHoveredIndex === index" >
<span
class="-top-6 -right-6 border-gray-500 absolute active:scale-90 w-7 h-7 hover:scale-150 transition bg-bg-light dark:bg-bg-dark rounded-full border-2"
title="Unmount personality">
<!-- UNMOUNT BUTTON -->
<svg aria-hidden="true" class="top-1 left-1 relative w-5 h-5 text-red-600 hover:text-red-500 "
fill="currentColor" viewBox="0 0 20 20" stroke-width="1"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd"></path>
</svg>
</span>
</button>
<button @click.prevent="remount_personality(item)" v-if="personalityHoveredIndex === index">
<span
class="-top-9 left-2 border-gray-500 active:scale-90 absolute items-center w-7 h-7 hover:scale-150 transition text-red-200 absolute active:scale-90 bg-bg-light dark:bg-bg-dark rounded-full border-2"
title="Remount">
<!-- UNMOUNT BUTTON -->
<svg xmlns="http://www.w3.org/2000/svg" class="top-1 left-1 relative w-4 h-4 text-red-600 hover:text-red-500 " viewBox="0 0 30 30" width="2" height="2" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
<g id="surface1">
<path style=" " d="M 16 4 C 10.886719 4 6.617188 7.160156 4.875 11.625 L 6.71875 12.375 C 8.175781 8.640625 11.710938 6 16 6 C 19.242188 6 22.132813 7.589844 23.9375 10 L 20 10 L 20 12 L 27 12 L 27 5 L 25 5 L 25 8.09375 C 22.808594 5.582031 19.570313 4 16 4 Z M 25.28125 19.625 C 23.824219 23.359375 20.289063 26 16 26 C 12.722656 26 9.84375 24.386719 8.03125 22 L 12 22 L 12 20 L 5 20 L 5 27 L 7 27 L 7 23.90625 C 9.1875 26.386719 12.394531 28 16 28 C 21.113281 28 25.382813 24.839844 27.125 20.375 Z "/>
</g>
</svg>
</span>
</button>
<button @click.prevent="handleOnTalk(item)" v-if="personalityHoveredIndex === index">
<span
class="-top-6 -left-6 border-gray-500 active:scale-90 absolute items-center w-7 h-7 hover:scale-150 transition text-red-200 absolute active:scale-90 bg-bg-light dark:bg-bg-dark rounded-full border-2"
title="Talk">
<!-- UNMOUNT BUTTON -->
<svg xmlns="http://www.w3.org/2000/svg" class="top-1 left-1 relative w-4 h-4 text-red-600 hover:text-red-500 " viewBox="0 0 24 24" width="2" height="2" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
<line x1="22" y1="2" x2="11" y2="13"></line>
<polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
</svg>
</span>
</button>
</div>
</div>
</div>
</div>
</div>
<MountedPersonalities ref="mountedPers" :onShowPersList="onShowPersListFun" :onReady="onPersonalitiesReadyFun"/>
</div>
<div class="w-fit">
<PersonalitiesCommands
v-if="personalities_ready && this.$store.state.mountedPersArr[this.$store.state.config.active_personality_id].commands!=''"
:commandsList="this.$store.state.mountedPersArr[this.$store.state.config.active_personality_id].commands"
:sendCommand="sendCMDEvent"
:on-show-toast-message="onShowToastMessage"
ref="personalityCMD"
></PersonalitiesCommands>
</div>
<div class="w-fit">
<PersonalitiesCommands
v-if="isDataSourceNamesValid"
:icon="'feather:book'"
:commandsList="dataSourceNames"
:sendCommand="mountDB"
:on-show-toast-message="onShowToastMessage"
ref="databasesList"
></PersonalitiesCommands>
</div>
<div class="relative grow">
<form>
<textarea
id="chat"
rows="1"
v-model="message"
@paste="handlePaste"
@keydown.enter.exact="submitOnEnter($event)"
class="w-full p-3 text-sm text-gray-900 dark:text-white bg-gray-100 dark:bg-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
placeholder="Send message..."
></textarea>
</form>
</div>
<div class="flex items-center space-x-3">
<ChatBarButton
v-if="loading"
@click="stopGenerating"
class="bg-red-500 dark:bg-red-600 hover:bg-red-600 dark:hover:bg-red-700"
>
<template #icon>
<svg class="animate-spin h-5 w-5" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</template>
<span>Stop</span>
</ChatBarButton>
<ChatBarButton v-else @click="submit" title="Send">
<template #icon>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"/>
</svg>
</template>
</ChatBarButton>
<ChatBarButton @click="submitWithInternetSearch" title="Send with internet search">
<template #icon>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"/>
</svg>
</template>
</ChatBarButton>
<ChatBarButton
@click="startSpeechRecognition"
:class="{ 'text-red-500': isListeningToVoice }"
title="Voice input"
>
<template #icon>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z"/>
</svg>
</template>
</ChatBarButton>
<ChatBarButton
v-if="$store.state.config.active_tts_service != 'None' && $store.state.config.active_tts_service != null && this.$store.state.config.active_stt_service!='None' && this.$store.state.config.active_stt_service!=null"
@click="is_rt ? stopRTCom : startRTCom"
:class="is_rt ? 'bg-red-500 dark:bg-red-600' : 'bg-green-500 dark:bg-green-600'"
title="Real-time audio mode"
>
<template #icon>
🪶
</template>
</ChatBarButton>
<ChatBarButton @click="add_file" title="Send file">
<template #icon>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</template>
</ChatBarButton>
<ChatBarButton @click="takePicture" title="Take picture">
<template #icon>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
</template>
</ChatBarButton>
<ChatBarButton @click="addWebLink" title="Add web link">
<template #icon>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/>
</svg>
</template>
</ChatBarButton>
<ChatBarButton @click="makeAnEmptyUserMessage" title="New user message" class="text-gray-600 dark:text-gray-300">
<template #icon>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/>
</svg>
</template>
</ChatBarButton>
<ChatBarButton @click="makeAnEmptyAIMessage" title="New AI message" class="text-red-400 dark:text-red-300">
<template #icon>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
</svg>
</template>
</ChatBarButton>
<ChatBarButton
@click="toggleRightPanel"
:class="{ 'text-red-500': !rightPanelCollapsed }"
title="Toggle View Mode"
>
<template #icon>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
<circle cx="12" cy="5" r="2"></circle>
<path d="M12 7v4"></path>
<line x1="8" y1="16" x2="8" y2="16"></line>
<line x1="16" y1="16" x2="16" y2="16"></line>
</svg> </template>
</ChatBarButton>
</div>
<input type="file" ref="fileDialog" @change="addFiles" multiple style="display: none" />
</div>
<div class="ml-auto gap-2">
</div>
</div>
</div>
<UniversalForm ref="universalForm" class="z-20" />

View File

@ -1,605 +1,111 @@
<template>
<header v-if="isFullMode" class="top-0 shadow-lg navbar-container">
<nav class="container flex flex-col lg:flex-row items-center gap-2 pb-0">
<!-- LOGO -->
<RouterLink :to="{ name: 'discussions' }" class="flex items-center space-x-2"> <!-- Added space-x-2 -->
<div class="logo-container"> <!-- Removed mr-1 -->
<img class="w-12 h-12 rounded-full object-cover logo-image"
:src="$store.state.config == null ? storeLogo : $store.state.config.app_custom_logo != '' ? '/user_infos/' + $store.state.config.app_custom_logo : storeLogo"
alt="Logo" title="LoLLMS WebUI">
</div>
<div class="flex flex-col justify-center">
<div class="text-6xl md:text-2xl font-bold text-amber-500 mb-2"
style="text-shadow: 2px 2px 0px white, -2px -2px 0px white, 2px -2px 0px white, -2px 2px 0px white;">
L🌟LLMS
</div>
<p class="text-gray-400 text-sm">One tool to rule them all</p>
</div>
</RouterLink>
<!-- SYSTEM STATUS -->
<div class="flex gap-3 flex-1 items-center justify-end">
<div v-if="isModelOK" title="Model is ok" class="text-green-500 dark:text-green-400 cursor-pointer transition-transform hover:scale-110">
<svg class="w-8 h-8" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 12L11 14L15 10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div v-else title="Model is not ok" class="text-red-500 dark:text-red-400 cursor-pointer transition-transform hover:scale-110">
<svg class="w-8 h-8" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15 9L9 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 9L15 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div v-if="!isGenerating" title="Text is not being generated. Ready to generate" class="text-green-500 dark:text-green-400 cursor-pointer transition-transform hover:scale-110">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 21v-4m0 0V5a2 2 0 012-2h6.5l1 1H21l-3 6 3 6h-8.5l-1-1H5a2 2 0 00-2 2zm9-13.5V9"></path>
</svg>
</div>
<div v-else title="Generation in progress..." class="text-yellow-500 dark:text-yellow-400 cursor-pointer transition-transform hover:scale-110">
<svg class="w-6 h-6 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
</svg>
</div>
<div v-if="isConnected" title="Connection status: Connected" class="text-green-500 dark:text-green-400 cursor-pointer transition-transform hover:scale-110">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
</svg>
</div>
<div v-else title="Connection status: Not connected" class="text-red-500 dark:text-red-400 cursor-pointer transition-transform hover:scale-110">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"></path>
</svg>
</div>
</div>
<div class="flex items-center space-x-4">
<ActionButton @click="restartProgram" icon="power" title="restart program" />
<ActionButton @click="refreshPage" icon="refresh-ccw" title="refresh page" />
<ActionButton href="/docs" icon="file-text" title="Fast API doc" />
</div>
<!-- SOCIALS -->
<div class="flex items-center space-x-4">
<SocialIcon href="https://github.com/ParisNeo/lollms-webui" icon="github" />
<SocialIcon href="https://www.youtube.com/channel/UCJzrg0cyQV2Z30SQ1v2FdSQ" icon="youtube" />
<SocialIcon href="https://x.com/ParisNeo_AI" icon="x" />
<SocialIcon href="https://discord.com/channels/1092918764925882418" icon="discord" />
</div>
<div class="relative group" title="Lollms News">
<div @click="showNews()" class="text-2xl w-8 h-8 cursor-pointer transition-colors duration-300 text-gray-600 hover:text-primary dark:text-gray-300 dark:hover:text-primary">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-full h-full">
<path d="M19 20H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v1m2 13a2 2 0 0 1-2-2V7m2 13a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"></path>
</svg>
</div>
<span class="absolute hidden group-hover:block bg-gray-800 text-white text-xs rounded py-1 px-2 top-full left-1/2 transform -translate-x-1/2 mt-2 whitespace-nowrap">
Lollms News
</span>
</div>
<div class="relative group">
<div
v-if="is_fun_mode"
title="Fun mode is on, press to turn off"
class="w-8 h-8 cursor-pointer text-green-500 dark:text-green-400 hover:text-green-600 dark:hover:text-green-300 transition-colors duration-300"
@click="fun_mode_off()"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-full h-full animate-bounce">
<circle cx="12" cy="12" r="10"></circle>
<path d="M8 14s1.5 2 4 2 4-2 4-2"></path>
<line x1="9" y1="9" x2="9.01" y2="9"></line>
<line x1="15" y1="9" x2="15.01" y2="9"></line>
</svg>
</div>
<div
v-else
title="Fun mode is off, press to turn on"
class="w-8 h-8 cursor-pointer text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors duration-300"
@click="fun_mode_on()"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-full h-full">
<circle cx="12" cy="12" r="10"></circle>
<line x1="8" y1="15" x2="16" y2="15"></line>
<line x1="9" y1="9" x2="9.01" y2="9"></line>
<line x1="15" y1="9" x2="15.01" y2="9"></line>
</svg>
</div>
<span class="absolute hidden group-hover:block bg-gray-800 text-white text-xs rounded py-1 px-2 top-full left-1/2 transform -translate-x-1/2 mb-2 whitespace-nowrap">
{{ is_fun_mode ? 'Turn off fun mode' : 'Turn on fun mode' }}
</span>
</div>
<div class="language-selector relative">
<button @click="toggleLanguageMenu" class="bg-transparent text-black dark:text-white py-1 px-1 rounded font-bold uppercase transition-colors duration-300 hover:bg-blue-500">
{{ $store.state.language.slice(0, 2) }}
</button>
<div v-if="isLanguageMenuVisible" ref="languageMenu" class="container language-menu absolute left-0 mt-1 bg-white dark:bg-bg-dark-tone rounded shadow-lg z-10 overflow-y-auto scrollbar-thin scrollbar-track-bg-light-tone scrollbar-thumb-bg-light-tone-panel hover:scrollbar-thumb-primary dark:scrollbar-track-bg-dark-tone dark:scrollbar-thumb-bg-dark-tone-panel dark:hover:scrollbar-thumb-primary active:scrollbar-thumb-secondary" style="position: absolute; top: 100%; width: 200px; max-height: 300px; overflow-y: auto;">
<ul style="list-style-type: none; padding-left: 0; margin-left: 0;">
<li v-for="language in languages" :key="language" class="relative flex items-center" style="padding-left: 0; margin-left: 0;">
<button @click="deleteLanguage(language)" class="mr-2 text-red-500 hover:text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-opacity-50 rounded-full"></button>
<div @click="selectLanguage(language)" :class="{'cursor-pointer hover:bg-blue-500 hover:text-white py-2 px-4 block whitespace-no-wrap': true, 'bg-blue-500 text-white': language === $store.state.language, 'flex-grow': true}">
{{ language }}
</div>
</li>
<li class="cursor-pointer hover:text-white py-0 px-0 block whitespace-no-wrap">
<input type="text" v-model="customLanguage" @keyup.enter.prevent="addCustomLanguage" placeholder="Enter language..." class="bg-transparent border border-gray-300 rounded py-0 px-0 mx-0 my-1 w-full">
</li>
</ul>
</div>
</div>
<div class="sun text-2xl w-6 hover:text-primary duration-150 cursor-pointer" title="Switch to Light theme" @click="themeSwitch()">
<i data-feather="sun"></i>
</div>
<div class="moon text-2xl w-6 hover:text-primary duration-150 cursor-pointer" title="Switch to Dark theme" @click="themeSwitch()">
<i data-feather="moon"></i>
</div>
</nav>
<!-- NAVIGATION BUTTONS -->
<Navigation />
<Toast ref="toast" />
<MessageBox ref="messageBox" />
<div v-show="progress_visibility" role="status" class="fixed m-0 p-2 left-2 bottom-2 min-w-[24rem] max-w-[24rem] h-20 flex flex-col justify-center items-center pb-4 bg-blue-500 rounded-lg shadow-lg z-50 background-a">
<ProgressBar ref="progress" :progress="progress_value" class="w-full h-4"></ProgressBar>
<p class="text-2xl animate-pulse mt-2 text-white">{{ loading_infos }} ...</p>
</div>
<UniversalForm ref="universalForm" class="z-20" />
<YesNoDialog ref="yesNoDialog" class="z-20" />
<PersonalityEditor ref="personality_editor" :config="currentPersonConfig" :personality="selectedPersonality"></PersonalityEditor>
<div id="app">
<PopupViewer ref="news"/>
<div class="dock-container" @mousemove="handleMouseMove" @mouseleave="handleMouseLeave">
<header class="navbar-container" :class="{ 'translate-y-0 opacity-100': showDock || isFullMode, '-translate-y-full opacity-0': !showDock && !isFullMode}">
<div class="flex flex-row items-center justify-between w-full max-w-screen-xl mx-auto px-4">
<!-- NAVIGATION BUTTONS -->
<Navigation />
<!-- PIN BUTTON -->
<button @click="togglePin" class="pin-button" :class="{ 'pinned': isFullMode }">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z" />
</svg>
</button>
</div>
</header>
</div>
</template>
<script setup>
import Toast from '@/components/Toast.vue'
import MessageBox from "@/components/MessageBox.vue";
import ProgressBar from "@/components/ProgressBar.vue";
import UniversalForm from '../components/UniversalForm.vue';
import YesNoDialog from './YesNoDialog.vue';
import PersonalityEditor from "@/components/PersonalityEditor.vue"
import PopupViewer from '@/components/PopupViewer.vue';
import ActionButton from '@/components/ActionButton.vue'
import SocialIcon from '@/components/SocialIcon.vue'
import FastAPI from '@/assets/fastapi.png';
import discord from '@/assets/discord.svg';
import { RouterLink } from 'vue-router'
import Navigation from './Navigation.vue'
import { nextTick } from 'vue'
import feather from 'feather-icons'
import static_info from "../assets/static_info.svg"
import animated_info from "../assets/animated_info.svg"
import { useRouter } from 'vue-router'
import storeLogo from '@/assets/logo.png'
import fun_mode from "../assets/fun_mode.svg"
import normal_mode from "../assets/normal_mode.svg"
import axios from 'axios';
import { store } from '../main';
</script>
<script>
import { ref, computed } from 'vue'
import { useStore } from 'vuex'
import Navigation from '@/components/Navigation.vue'
import { store } from '../main';
export default {
name: 'TopBar',
computed:{
isFullMode() {
return this.$store.state.view_mode === 'full'; // Accessing the mode directly
},
storeLogo(){
if (this.$store.state.config){
return storeLogo
}
return this.$store.state.config.app_custom_logo!=''?'/user_infos/'+this.$store.state.config.app_custom_logo:storeLogo
},
languages: {
get(){
console.log("searching languages", this.$store.state.languages)
return this.$store.state.languages
}
},
language: {
get(){
console.log("searching language", this.$store.state.language)
return this.$store.state.language
}
},
currentPersonConfig (){
try{
return this.$store.state.currentPersonConfig
}
catch{
console.log("Error finding current personality configuration")
return undefined
}
},
selectedPersonality (){
try{
return this.$store.state.selectedPersonality
}
catch{
console.log("Error finding current personality configuration")
return undefined
}
},
loading_infos(){
return this.$store.state.loading_infos;
},
is_fun_mode(){
try{
if (this.$store.state.config){
return this.$store.state.config.fun_mode;
}
else{
return false;
}
}
catch(error){
console.error("Oopsie! Looks like we hit a snag: ", error);
return false;
}
},
isModelOK(){
return this.$store.state.isModelOk;
},
isGenerating(){
return this.$store.state.isGenerating;
},
isConnected(){
return this.$store.state.isConnected;
}
},
components: {
Toast,
MessageBox,
ProgressBar,
UniversalForm,
YesNoDialog,
Navigation,
PersonalityEditor,
PopupViewer,
ActionButton,
SocialIcon
},
watch:{
'$store.state.config.fun_mode': function(newVal, oldVal) {
console.log(`Fun mode changed from ${oldVal} to ${newVal}! 🎉`);
},
'$store.state.isConnected': function(newVal, oldVal) {
if (!this.isConnected){
this.$store.state.messageBox.showBlockingMessage("Server suddenly disconnected. Please reboot the server to recover the connection")
this.is_first_connection = false
console.log("this.is_first_connection set to false")
console.log(this.is_first_connection)
if(this.$store.state.config.activate_audio_infos)
this.connection_lost_audio.play()
}
else{
console.log("this.is_first_connection")
console.log(this.is_first_connection)
if(!this.is_first_connection){
this.$store.state.messageBox.hideMessage()
this.$store.state.messageBox.showMessage("Server connected.")
if(this.$store.state.config.activate_audio_infos)
this.connection_recovered_audio.play()
}
}
nextTick(() => {
feather.replace()
})
}
name: 'TopBar',
components: {
Navigation
},
computed:{
isFullMode() {
return store.state.view_mode === 'full'; // Accessing the mode directly
},
},
setup() {
const store = useStore()
const showDock = ref(false)
const isFullMode = computed(() => store.state.view_mode === 'full')
},
data() {
return {
customLanguage: '', // Holds the value of the custom language input
selectedLanguage: '',
isLanguageMenuVisible: false,
static_info: static_info,
animated_info: animated_info,
normal_mode:normal_mode,
fun_mode:fun_mode,
is_first_connection:true,
discord:discord,
FastAPI:FastAPI,
rebooting_audio: new Audio("rebooting.wav"),
connection_lost_audio: new Audio("connection_lost.wav"),
connection_recovered_audio: new Audio("connection_recovered.wav"),
database_selectorDialogVisible:false,
progress_visibility:false,
progress_value:0,
codeBlockStylesheet:'',
sunIcon: document.querySelector(".sun"),
moonIcon: document.querySelector(".moon"),
userTheme: localStorage.getItem("theme"),
systemTheme: window.matchMedia("prefers-color-scheme: dark").matches,
posts_headers : {
'accept': 'application/json',
'Content-Type': 'application/json'
}
}
},
async mounted() {
this.$store.state.toast = this.$refs.toast
this.$store.state.news = this.$refs.news
this.$store.state.messageBox = this.$refs.messageBox
this.$store.state.universalForm = this.$refs.universalForm
this.$store.state.yesNoDialog = this.$refs.yesNoDialog
this.$store.state.personality_editor = this.$refs.personality_editor
const handleMouseMove = (event) => {
if (!isFullMode.value) {
showDock.value = event.clientY <= 50
}
}
this.sunIcon = document.querySelector(".sun");
this.moonIcon = document.querySelector(".moon");
this.userTheme = localStorage.getItem("theme");
this.systemTheme = window.matchMedia("prefers-color-scheme: dark").matches;
this.themeCheck()
const handleMouseLeave = () => {
if (!isFullMode.value) {
showDock.value = false
}
}
nextTick(() => {
feather.replace()
})
const togglePin = () => {
const newMode = store.state.view_mode=='compact' ? 'full' : 'compact';
store.commit('setViewMode', newMode); // Assuming you have a mutation to set the view mode
showDock.value = isFullMode.value
}
window.addEventListener('resize', this.adjustMenuPosition);
},
beforeUnmount() {
window.removeEventListener('resize', this.adjustMenuPosition);
},
created() {
this.sunIcon = document.querySelector(".sun");
this.moonIcon = document.querySelector(".moon");
this.userTheme = localStorage.getItem("theme");
this.systemTheme = window.matchMedia("prefers-color-scheme: dark").matches;
if (!localStorage.getItem('lollms_webui_view_mode')) {
localStorage.setItem('lollms_webui_view_mode', 'compact'); // Default to 'compact'
}
},
methods: {
adjustMenuPosition() {
const menu = this.$refs.languageMenu;
if(menu){
const rect = menu.getBoundingClientRect();
const windowWidth = window.innerWidth;
if (rect.right > windowWidth) {
menu.style.left = 'auto';
menu.style.right = '0';
} else {
menu.style.left = '0';
menu.style.right = 'auto';
}
}
},
addCustomLanguage() {
if (this.customLanguage.trim() !== '') {
this.selectLanguage(this.customLanguage);
this.customLanguage = ''; // Reset the input field after adding
}
},
async selectLanguage(language) {
await this.$store.dispatch('changeLanguage', language);
this.toggleLanguageMenu(); // Fermer le menu après le changement de langue
this.language = language
},
async deleteLanguage(language) {
await this.$store.dispatch('deleteLanguage', language);
this.toggleLanguageMenu(); // Fermer le menu après le changement de langue
this.language = language
},
toggleLanguageMenu() {
console.log("Toggling language ",this.isLanguageMenuVisible)
this.isLanguageMenuVisible = !this.isLanguageMenuVisible;
},
restartProgram(event) {
event.preventDefault();
this.$store.state.api_post_req('restart_program', this.$store.state.client_id)
this.rebooting_audio.play()
this.$store.state.toast.showToast("Rebooting the app. Please wait...", 410, false)
//this.$store.state.toast.showToast("Rebooting the app. Please wait...", 50, true);
console.log("this.$store.state.api_get_req",this.$store.state.api_get_req)
setTimeout(()=>{
window.close();
},2000)
},
refreshPage() {
const hostnameParts = window.location.href.split('/');
if(hostnameParts.length > 4){
window.location.href='/'
}
else{
window.location.reload(true);
}
},
handleOk(inputText) {
console.log("Input text:", inputText);
},
// codeBlockTheme(theme) {
// const styleDark = document.createElement('link');
// styleDark.type = "text/css";
// styleDark.href = 'highlight.js/styles/tokyo-night-dark.css';
// const styleLight = document.createElement('link');
// styleLight.type = "text/css";
// styleLight.href = 'highlight.js/styles/tomorrow-night-blue.css';
// if(theme=='dark'){
// document.head.appendChild(styleDark);
// document.head.removeChild(styleLight);
// }else{
// document.head.appendChild(styleLight);
// //document.head.removeChild(styleDark);
// }
// },
applyConfiguration() {
this.isLoading = true;
console.log(this.$store.state.config)
axios.post('/apply_settings', {"client_id":this.$store.state.client_id, "config":this.$store.state.config}, {headers: this.posts_headers}).then((res) => {
this.isLoading = false;
//console.log('apply-res',res)
if (res.data.status) {
this.$store.state.toast.showToast("Configuration changed successfully.", 4, true)
this.settingsChanged = false
//this.save_configuration()
} else {
this.$store.state.toast.showToast("Configuration change failed.", 4, false)
}
nextTick(() => {
feather.replace()
})
})
},
fun_mode_on(){
console.log("Turning on fun mode")
this.$store.state.config.fun_mode=true;
this.applyConfiguration()
},
fun_mode_off(){
console.log("Turning off fun mode")
this.$store.state.config.fun_mode=false;
this.applyConfiguration()
},
showNews(){
this.$store.state.news.show()
},
themeCheck() {
if (this.userTheme == "dark" || (!this.userTheme && this.systemTheme)) {
document.documentElement.classList.add("dark");
this.moonIcon.classList.add("display-none");
nextTick(()=>{
//import('highlight.js/styles/tokyo-night-dark.css');
import('highlight.js/styles/stackoverflow-dark.css');
})
return
}
nextTick(()=>{
//import('highlight.js/styles/tomorrow-night-blue.css');
import('highlight.js/styles/stackoverflow-light.css');
})
this.sunIcon.classList.add("display-none")
},
themeSwitch() {
if (document.documentElement.classList.contains("dark")) {
document.documentElement.classList.remove("dark");
localStorage.setItem("theme", "light")
this.userTheme == "light"
this.iconToggle()
return
}
import('highlight.js/styles/tokyo-night-dark.css');
document.documentElement.classList.add("dark");
localStorage.setItem("theme", "dark")
this.userTheme == "dark"
this.iconToggle()
// Dispatch the themeChanged event
window.dispatchEvent(new Event('themeChanged'));
},
iconToggle() {
this.sunIcon.classList.toggle("display-none");
this.moonIcon.classList.toggle("display-none");
}
},
return {
showDock,
handleMouseMove,
handleMouseLeave,
togglePin
}
}
}
</script>
<style>
.dot {
width: 10px;
height: 10px;
border-radius: 50%;
<style scoped>
.dock-container {
@apply fixed flex m-[50px] flex-row top-0 left-0 right-0 h-[50px] z-50;
}
.dot-green {
background-color: green;
.navbar-container {
@apply fixed top-0 left-0 right-0 flex justify-center items-center
py-2 rounded-b-2xl shadow-lg bg-white bg-opacity-80 backdrop-blur-sm
transition-all duration-300 ease-in-out;
}
.dot-red {
background-color: red;
}
.animate-pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: .7;
}
}
.logo-container {
position: relative;
width: 48px;
height: 48px;
}
.logo-image {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
}
@keyframes bounce {
0%, 100% {
transform: translateY(-25%);
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
}
50% {
transform: translateY(0);
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
}
}
.animate-bounce {
animation: bounce 1s infinite;
.dark .navbar-container {
@apply bg-gray-800 bg-opacity-80;
}
@keyframes roll-and-bounce {
0%, 100% {
transform: translateX(0) rotate(0deg);
}
45% {
transform: translateX(100px) rotate(360deg);
}
50% {
transform: translateX(90px) rotate(390deg);
}
55% {
transform: translateX(100px) rotate(360deg);
}
95% {
transform: translateX(0) rotate(0deg);
}
.pin-button {
@apply p-2 rounded-full transition-colors duration-200 z-10;
background-color: rgba(255, 255, 255, 0.8);
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
.pin-button:hover {
@apply bg-gray-200;
}
</style>
.dark .pin-button {
background-color: rgba(31, 41, 55, 0.8);
}
.dark .pin-button:hover {
@apply bg-gray-700;
}
.pin-button.pinned {
@apply text-blue-500;
}
.dark .pin-button.pinned {
@apply text-blue-400;
}
</style>

View File

@ -54,20 +54,35 @@
<div v-if="showLeftPanel"
class="relative flex flex-col no-scrollbar shadow-lg min-w-[15rem] max-w-[15rem]"
>
<div class="toolbar discussion">
<!-- Toolbar container -->
<div class="toolbar-container">
<!-- "+" button -->
<button
class="toolbar-button"
title="Create new discussion"
@click="createNewDiscussion"
>
<i data-feather="plus"></i>
</button>
<RouterLink :to="{ name: 'discussions' }" class="flex items-center space-x-2"> <!-- Added space-x-2 -->
<div class="logo-container"> <!-- Removed mr-1 -->
<img class="w-12 h-12 rounded-full object-cover logo-image"
:src="$store.state.config == null ? storeLogo : $store.state.config.app_custom_logo != '' ? '/user_infos/' + $store.state.config.app_custom_logo : storeLogo"
alt="Logo" title="LoLLMS WebUI">
</div>
<div class="flex flex-col justify-center">
<div class="text-6xl md:text-2xl font-bold text-amber-500 mb-2"
style="text-shadow: 2px 2px 0px white, -2px -2px 0px white, 2px -2px 0px white, -2px 2px 0px white;">
L🌟LLMS
</div>
<p class="text-gray-400 text-sm">One tool to rule them all</p>
</div>
</RouterLink>
<!-- Menu container -->
<div class="menu-container">
<div class="toolbar discussion">
<!-- Toolbar container -->
<div class="toolbar-container">
<!-- "+" button -->
<button
class="toolbar-button"
title="Create new discussion"
@click="createNewDiscussion"
>
<i data-feather="plus"></i>
</button>
<!-- Menu container -->
<div class="menu-container">
<!-- Menu toggle button -->
<button
class="toolbar-button"
@ -78,58 +93,56 @@
</button>
<!-- Expandable menu -->
<div class="expandable-menu discussion z-50 p-4 bg-white rounded-lg shadow-lg" :class="{ 'hidden': !isMenuVisible }">
<div>
<!-- Edit discussion list -->
<button class="text-3xl hover:text-secondary dark:hover:text-secondary-light duration-150 active:scale-95" title="Edit discussion list" type="button" @click="isCheckbox = !isCheckbox" :class="isCheckbox ? 'text-secondary dark:text-secondary-light' : 'text-gray-700 dark:text-gray-300'">
<i data-feather="check-square"></i>
</button>
<div v-if="isMenuVisible" class="expandable-menu discussion z-50 p-4 bg-white dark:bg-bg-dark rounded-lg shadow-lg">
<!-- Edit discussion list -->
<button class="text-3xl hover:text-secondary dark:hover:text-secondary-light duration-150 active:scale-95" title="Edit discussion list" type="button" @click="isCheckbox = !isCheckbox" :class="isCheckbox ? 'text-secondary dark:text-secondary-light' : 'text-gray-700 dark:text-gray-300'">
<i data-feather="check-square"></i>
</button>
<!-- Reset database -->
<button class="text-3xl hover:text-red-500 dark:hover:text-red-400 duration-150 active:scale-95" title="Reset database, remove all discussions" @click.stop="">
<i data-feather="trash-2"></i>
</button>
<!-- Reset database -->
<button class="text-3xl hover:text-red-500 dark:hover:text-red-400 duration-150 active:scale-95" title="Reset database, remove all discussions" @click.stop="">
<i data-feather="trash-2"></i>
</button>
<!-- Export database -->
<button class="text-3xl hover:text-secondary dark:hover:text-secondary-light duration-150 active:scale-95" title="Export database" type="button" @click.stop="database_selectorDialogVisible=true">
<i data-feather="database"></i>
</button>
<!-- Export database -->
<button class="text-3xl hover:text-secondary dark:hover:text-secondary-light duration-150 active:scale-95" title="Export database" type="button" @click.stop="database_selectorDialogVisible=true">
<i data-feather="database"></i>
</button>
<!-- Import discussions -->
<input type="file" ref="fileDialog" class="hidden" @change="importDiscussions" />
<button class="text-3xl hover:text-secondary dark:hover:text-secondary-light duration-150 active:scale-95 rotate-90" title="Import discussions" type="button" @click.stop="$refs.fileDialog.click()">
<i data-feather="log-in"></i>
</button>
<!-- Import discussions -->
<input type="file" ref="fileDialog" class="hidden" @change="importDiscussions" />
<button class="text-3xl hover:text-secondary dark:hover:text-secondary-light duration-150 active:scale-95 rotate-90" title="Import discussions" type="button" @click.stop="$refs.fileDialog.click()">
<i data-feather="log-in"></i>
</button>
<!-- Import discussion bundle -->
<input type="file" ref="bundleLoadingDialog" class="hidden" @change="importDiscussionsBundle" />
<button v-if="!showSaveConfirmation" title="Import discussion bundle" @click.stop="$refs.bundleLoadingDialog.click()" class="text-3xl hover:text-secondary dark:hover:text-secondary-light duration-150 active:scale-95">
<i data-feather="folder"></i>
</button>
<!-- Import discussion bundle -->
<input type="file" ref="bundleLoadingDialog" class="hidden" @change="importDiscussionsBundle" />
<button v-if="!showSaveConfirmation" title="Import discussion bundle" @click.stop="$refs.bundleLoadingDialog.click()" class="text-3xl hover:text-secondary dark:hover:text-secondary-light duration-150 active:scale-95">
<i data-feather="folder"></i>
</button>
<!-- Filter discussions -->
<button class="text-3xl hover:text-secondary dark:hover:text-secondary-light duration-150 active:scale-95" title="Filter discussions" type="button" @click="isSearch = !isSearch" :class="isSearch ? 'text-secondary dark:text-secondary-light' : 'text-gray-700 dark:text-gray-300'">
<i data-feather="search"></i>
</button>
<!-- Filter discussions -->
<button class="text-3xl hover:text-secondary dark:hover:text-secondary-light duration-150 active:scale-95" title="Filter discussions" type="button" @click="isSearch = !isSearch" :class="isSearch ? 'text-secondary dark:text-secondary-light' : 'text-gray-700 dark:text-gray-300'">
<i data-feather="search"></i>
</button>
<!-- Add to skills database -->
<button v-if="!loading" type="button" @click.stop="addDiscussion2SkillsLibrary" title="Add this discussion content to skills database" class="text-3xl hover:text-secondary dark:hover:text-secondary-light duration-150 active:scale-95">
<i data-feather="hard-drive"></i>
</button>
<!-- Add to skills database -->
<button v-if="!loading" type="button" @click.stop="addDiscussion2SkillsLibrary" title="Add this discussion content to skills database" class="text-3xl hover:text-secondary dark:hover:text-secondary-light duration-150 active:scale-95">
<i data-feather="hard-drive"></i>
</button>
<!-- Toggle skills database -->
<button v-if="!loading && $store.state.config.activate_skills_lib" type="button" @click.stop="toggleSkillsLib" title="Skills database is activated" class="text-3xl hover:text-secondary dark:hover:text-secondary-light duration-150 active:scale-95">
<i data-feather="check-circle"></i>
</button>
<button v-if="!loading && !$store.state.config.activate_skills_lib" type="button" @click.stop="toggleSkillsLib" title="Skills database is deactivated" class="text-3xl hover:text-secondary dark:hover:text-secondary-light duration-150 active:scale-95">
<i data-feather="x-octagon"></i>
</button>
<!-- Toggle skills database -->
<button v-if="!loading && $store.state.config.activate_skills_lib" type="button" @click.stop="toggleSkillsLib" title="Skills database is activated" class="text-3xl hover:text-secondary dark:hover:text-secondary-light duration-150 active:scale-95">
<i data-feather="check-circle"></i>
</button>
<button v-if="!loading && !$store.state.config.activate_skills_lib" type="button" @click.stop="toggleSkillsLib" title="Skills database is deactivated" class="text-3xl hover:text-secondary dark:hover:text-secondary-light duration-150 active:scale-95">
<i data-feather="x-octagon"></i>
</button>
<!-- Show skills database -->
<button v-if="!loading" type="button" @click.stop="showSkillsLib" title="Show Skills database" class="text-3xl hover:text-secondary dark:hover:text-secondary-light duration-150 active:scale-95">
<i data-feather="book"></i>
</button>
</div>
<!-- Show skills database -->
<button v-if="!loading" type="button" @click.stop="showSkillsLib" title="Show Skills database" class="text-3xl hover:text-secondary dark:hover:text-secondary-light duration-150 active:scale-95">
<i data-feather="book"></i>
</button>
<!-- Loading spinner -->
<div v-if="loading" title="Loading.." class="flex justify-center mt-4">
@ -158,43 +171,136 @@
<button @click="importChatGPT" class="text-sm hover:text-secondary dark:hover:text-secondary-light">ChatGPT</button>
</div>
</div>
<!-- Checkbox operations -->
<div v-if="isCheckbox" class="absolute top-0 left-12 w-64 p-4 bg-bg-light dark:bg-bg-dark">
<div class="flex flex-col space-y-2">
<p v-if="selectedDiscussions.length > 0">Selected: {{ selectedDiscussions.length }}</p>
<div v-if="selectedDiscussions.length > 0" class="flex space-x-2">
<button v-if="!showConfirmation" class="text-2xl hover:text-red-600 duration-75 active:scale-90" title="Remove selected" type="button" @click.stop="showConfirmation = true">
<i data-feather="trash"></i>
</button>
<div v-if="showConfirmation" class="flex space-x-2">
<button class="text-2xl hover:text-secondary duration-75 active:scale-90" title="Confirm removal" type="button" @click.stop="deleteDiscussionMulti">
<i data-feather="check"></i>
</button>
<button class="text-2xl hover:text-red-600 duration-75 active:scale-90" title="Cancel removal" type="button" @click.stop="showConfirmation = false">
<i data-feather="x"></i>
</button>
<button @click="toggleInfosMenu" title="Infos">
<svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" viewBox="0 0 25 25">
<!-- Circle background -->
<circle cx="12.5" cy="12.5" r="11.25" fill="#3498db"/>
<!-- "i" stem -->
<rect x="11.25" y="10" width="2.5" height="8.75" fill="white"/>
<!-- "i" dot -->
<circle cx="12.5" cy="6.25" r="1.25" fill="white"/>
</svg>
</button>
<!-- Menu Content -->
<nav v-if="isinfosMenuVisible" class="expandable-menu discussion z-50 p-4 bg-white dark:bg-bg-dark rounded-lg shadow-lg ">
<div class="container flex flex-col lg:flex-row items-center gap-2 p-4">
<!-- SYSTEM STATUS -->
<div class="flex gap-3 flex-1 items-center justify-end">
<div v-if="isModelOK" title="Model is ok" class="text-green-500 dark:text-green-400 cursor-pointer transition-transform hover:scale-110">
<svg class="w-8 h-8" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 12L11 14L15 10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div v-else title="Model is not ok" class="text-red-500 dark:text-red-400 cursor-pointer transition-transform hover:scale-110">
<svg class="w-8 h-8" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15 9L9 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 9L15 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div v-if="!isGenerating" title="Text is not being generated. Ready to generate" class="text-green-500 dark:text-green-400 cursor-pointer transition-transform hover:scale-110">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 21v-4m0 0V5a2 2 0 012-2h6.5l1 1H21l-3 6 3 6h-8.5l-1-1H5a2 2 0 00-2 2zm9-13.5V9"></path>
</svg>
</div>
<div v-else title="Generation in progress..." class="text-yellow-500 dark:text-yellow-400 cursor-pointer transition-transform hover:scale-110">
<svg class="w-6 h-6 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
</svg>
</div>
<div v-if="isConnected" title="Connection status: Connected" class="text-green-500 dark:text-green-400 cursor-pointer transition-transform hover:scale-110">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
</svg>
</div>
<div v-else title="Connection status: Not connected" class="text-red-500 dark:text-red-400 cursor-pointer transition-transform hover:scale-110">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"></path>
</svg>
</div>
</div>
</div>
<div class="flex space-x-2">
<button class="text-2xl hover:text-secondary duration-75 active:scale-90 rotate-90" title="Export selected to a json file" type="button" @click.stop="exportDiscussionsAsJson">
<i data-feather="codepen"></i>
</button>
<button class="text-2xl hover:text-secondary duration-75 active:scale-90 rotate-90" title="Export selected to a markdown file" type="button" @click.stop="exportDiscussions">
<i data-feather="folder"></i>
</button>
<button class="text-2xl hover:text-secondary duration-75 active:scale-90 rotate-90" title="Export selected to a markdown file" type="button" @click.stop="exportDiscussionsAsMarkdown">
<i data-feather="bookmark"></i>
</button>
<button class="text-2xl hover:text-secondary duration-75 active:scale-90" title="Select All" type="button" @click.stop="selectAllDiscussions">
<i data-feather="list"></i>
</button>
</div>
<div class="flex items-center space-x-4">
<ActionButton @click="restartProgram" icon="power" title="restart program" />
<ActionButton @click="refreshPage" icon="refresh-ccw" title="refresh page" />
<ActionButton href="/docs" icon="file-text" title="Fast API doc" />
</div>
<!-- SOCIALS -->
<SocialIcon href="https://github.com/ParisNeo/lollms-webui" icon="github" />
<SocialIcon href="https://www.youtube.com/channel/UCJzrg0cyQV2Z30SQ1v2FdSQ" icon="youtube" />
<SocialIcon href="https://x.com/ParisNeo_AI" icon="x" />
<SocialIcon href="https://discord.com/channels/1092918764925882418" icon="discord" />
<div class="relative group" title="Lollms News">
<div @click="showNews()" class="text-2xl w-8 h-8 cursor-pointer transition-colors duration-300 text-gray-600 hover:text-primary dark:text-gray-300 dark:hover:text-primary">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-full h-full">
<path d="M19 20H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v1m2 13a2 2 0 0 1-2-2V7m2 13a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"></path>
</svg>
</div>
<span class="absolute hidden group-hover:block bg-gray-800 text-white text-xs rounded py-1 px-2 top-full left-1/2 transform -translate-x-1/2 mt-2 whitespace-nowrap">
Lollms News
</span>
</div>
<div
v-if="is_fun_mode"
title="Fun mode is on, press to turn off"
class="w-8 h-8 cursor-pointer text-green-500 dark:text-green-400 hover:text-green-600 dark:hover:text-green-300 transition-colors duration-300"
@click="fun_mode_off()"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-full h-full animate-bounce">
<circle cx="12" cy="12" r="10"></circle>
<path d="M8 14s1.5 2 4 2 4-2 4-2"></path>
<line x1="9" y1="9" x2="9.01" y2="9"></line>
<line x1="15" y1="9" x2="15.01" y2="9"></line>
</svg>
</div>
<div
v-else
title="Fun mode is off, press to turn on"
class="w-8 h-8 cursor-pointer text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors duration-300"
@click="fun_mode_on()"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-full h-full">
<circle cx="12" cy="12" r="10"></circle>
<line x1="8" y1="15" x2="16" y2="15"></line>
<line x1="9" y1="9" x2="9.01" y2="9"></line>
<line x1="15" y1="9" x2="15.01" y2="9"></line>
</svg>
</div>
<span class="absolute hidden group-hover:block bg-gray-800 text-white text-xs rounded py-1 px-2 top-full left-1/2 transform -translate-x-1/2 mb-2 whitespace-nowrap">
{{ is_fun_mode ? 'Turn off fun mode' : 'Turn on fun mode' }}
</span>
<div class="language-selector relative">
<button @click="toggleLanguageMenu" class="bg-transparent text-black dark:text-white py-1 px-1 rounded font-bold uppercase transition-colors duration-300 hover:bg-blue-500">
{{ $store.state.language.slice(0, 2) }}
</button>
<div v-if="isLanguageMenuVisible" ref="languageMenu" class="container language-menu absolute left-0 mt-1 bg-white dark:bg-bg-dark-tone rounded shadow-lg z-10 overflow-y-auto scrollbar-thin scrollbar-track-bg-light-tone scrollbar-thumb-bg-light-tone-panel hover:scrollbar-thumb-primary dark:scrollbar-track-bg-dark-tone dark:scrollbar-thumb-bg-dark-tone-panel dark:hover:scrollbar-thumb-primary active:scrollbar-thumb-secondary" style="position: absolute; top: 100%; width: 200px; max-height: 300px; overflow-y: auto;">
<ul style="list-style-type: none; padding-left: 0; margin-left: 0;">
<li v-for="language in languages" :key="language" class="relative flex items-center" style="padding-left: 0; margin-left: 0;">
<button @click="deleteLanguage(language)" class="mr-2 text-red-500 hover:text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-opacity-50 rounded-full"></button>
<div @click="selectLanguage(language)" :class="{'cursor-pointer hover:bg-blue-500 hover:text-white py-2 px-4 block whitespace-no-wrap': true, 'bg-blue-500 text-white': language === $store.state.language, 'flex-grow': true}">
{{ language }}
</div>
</li>
<li class="cursor-pointer hover:text-white py-0 px-0 block whitespace-no-wrap">
<input type="text" v-model="customLanguage" @keyup.enter.prevent="addCustomLanguage" placeholder="Enter language..." class="bg-transparent border border-gray-300 rounded py-0 px-0 mx-0 my-1 w-full">
</li>
</ul>
</div>
</div>
<div class="sun text-2xl w-6 hover:text-primary duration-150 cursor-pointer" title="Switch to Light theme" @click="themeSwitch()">
<i data-feather="sun"></i>
</div>
<div class="moon text-2xl w-6 hover:text-primary duration-150 cursor-pointer" title="Switch to Dark theme" @click="themeSwitch()">
<i data-feather="moon"></i>
</div>
</div>
</nav>
</div>
</div>
</div>
</div>
</div>
<!-- Search bar -->
@ -214,6 +320,39 @@
</div>
</div>
<!-- Checkbox operations -->
<div v-if="isCheckbox" class="w-full p-4 bg-bg-light dark:bg-bg-dark">
<div class="flex flex-col space-y-2">
<p v-if="selectedDiscussions.length > 0">Selected: {{ selectedDiscussions.length }}</p>
<div v-if="selectedDiscussions.length > 0" class="flex space-x-2">
<button v-if="!showConfirmation" class="text-2xl hover:text-red-600 duration-75 active:scale-90" title="Remove selected" type="button" @click.stop="showConfirmation = true">
<i data-feather="trash"></i>
</button>
<div v-if="showConfirmation" class="flex space-x-2">
<button class="text-2xl hover:text-secondary duration-75 active:scale-90" title="Confirm removal" type="button" @click.stop="deleteDiscussionMulti">
<i data-feather="check"></i>
</button>
<button class="text-2xl hover:text-red-600 duration-75 active:scale-90" title="Cancel removal" type="button" @click.stop="showConfirmation = false">
<i data-feather="x"></i>
</button>
</div>
</div>
<div class="flex space-x-2">
<button class="text-2xl hover:text-secondary duration-75 active:scale-90 rotate-90" title="Export selected to a json file" type="button" @click.stop="exportDiscussionsAsJson">
<i data-feather="codepen"></i>
</button>
<button class="text-2xl hover:text-secondary duration-75 active:scale-90 rotate-90" title="Export selected to a markdown file" type="button" @click.stop="exportDiscussions">
<i data-feather="folder"></i>
</button>
<button class="text-2xl hover:text-secondary duration-75 active:scale-90 rotate-90" title="Export selected to a markdown file" type="button" @click.stop="exportDiscussionsAsMarkdown">
<i data-feather="bookmark"></i>
</button>
<button class="text-2xl hover:text-secondary duration-75 active:scale-90" title="Select All" type="button" @click.stop="selectAllDiscussions">
<i data-feather="list"></i>
</button>
</div>
</div>
</div>
<!-- LEFT SIDE PANEL -->
<div id="leftPanel" class="flex flex-col flex-grow overflow-y-scroll overflow-x-hidden custom-scrollbar "
@dragover.stop.prevent="setDropZoneDiscussion()">
@ -246,7 +385,7 @@
</div>
</div>
</div>
<div class="flex flex-row">
<div class="flex flex-row panels-color">
<div class="h-15 w-full py-4 cursor-pointer text-light-text-panel dark:text-dark-text-panel hover:text-secondary" @click="showDatabaseSelector">
<p class="text-center font-large font-bold text-l drop-shadow-md align-middle">{{ formatted_database_name.replace("_"," ") }}</p>
</div>
@ -352,6 +491,18 @@
</div>
<InputBox prompt-text="Enter the url to the page to use as discussion support" @ok="addWebpage" ref="web_url_input_box"></InputBox>
<SkillsLibraryViewer ref="skills_lib" ></SkillsLibraryViewer>
<Toast ref="toast" />
<MessageBox ref="messageBox" />
<div v-show="progress_visibility" role="status" class="fixed m-0 p-2 left-2 bottom-2 min-w-[24rem] max-w-[24rem] h-20 flex flex-col justify-center items-center pb-4 bg-blue-500 rounded-lg shadow-lg z-50 background-a">
<ProgressBar ref="progress" :progress="progress_value" class="w-full h-4"></ProgressBar>
<p class="text-2xl animate-pulse mt-2 text-white">{{ loading_infos }} ...</p>
</div>
<UniversalForm ref="universalForm" class="z-20" />
<YesNoDialog ref="yesNoDialog" class="z-20" />
<PersonalityEditor ref="personality_editor" :config="currentPersonConfig" :personality="selectedPersonality"></PersonalityEditor>
<div id="app">
<PopupViewer ref="news"/>
</div>
</template>
@ -603,8 +754,115 @@ animation: custom-pulse 2s infinite;
.menu-item:hover {
background-color: #f0f0f0;
}
.dot {
width: 10px;
height: 10px;
border-radius: 50%;
}
.dot-green {
background-color: green;
}
.dot-red {
background-color: red;
}
.animate-pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: .7;
}
}
.logo-container {
position: relative;
width: 48px;
height: 48px;
}
.logo-image {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
}
@keyframes bounce {
0%, 100% {
transform: translateY(-25%);
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
}
50% {
transform: translateY(0);
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
}
}
.animate-bounce {
animation: bounce 1s infinite;
}
@keyframes roll-and-bounce {
0%, 100% {
transform: translateX(0) rotate(0deg);
}
45% {
transform: translateX(100px) rotate(360deg);
}
50% {
transform: translateX(90px) rotate(390deg);
}
55% {
transform: translateX(100px) rotate(360deg);
}
95% {
transform: translateX(0) rotate(0deg);
}
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
<script>
import Toast from '@/components/Toast.vue'
import MessageBox from "@/components/MessageBox.vue";
import ProgressBar from "@/components/ProgressBar.vue";
import UniversalForm from '../components/UniversalForm.vue';
import YesNoDialog from '../components/YesNoDialog.vue';
import PersonalityEditor from "@/components/PersonalityEditor.vue"
import PopupViewer from '@/components/PopupViewer.vue';
import ActionButton from '@/components/ActionButton.vue'
import SocialIcon from '@/components/SocialIcon.vue'
import FastAPI from '@/assets/fastapi.png';
import discord from '@/assets/discord.svg';
import { RouterLink } from 'vue-router'
import { nextTick } from 'vue'
import feather from 'feather-icons'
import static_info from "../assets/static_info.svg"
import animated_info from "../assets/animated_info.svg"
import { useRouter } from 'vue-router'
import storeLogo from '@/assets/logo.png'
import fun_mode from "../assets/fun_mode.svg"
import normal_mode from "../assets/normal_mode.svg"
import axios from 'axios';
import { store } from '../main';
import SVGRedBrain from '@/assets/brain_red.svg';
import SVGOrangeBrain from '@/assets/brain_orange.svg';
import SVGGreenBrain from '@/assets/brain_green.svg';
@ -614,7 +872,6 @@ import inactive_skills from "../assets/inactive.svg"
import skillsRegistry from "../assets/registry.svg"
import robot from "../assets/robot.svg"
import { mapState } from 'vuex';
export default {
setup() { },
@ -622,6 +879,30 @@ export default {
data() {
return {
isMenuVisible: false,
isinfosMenuVisible: false,
isNavMenuVisible: false,
static_info: static_info,
animated_info: animated_info,
normal_mode:normal_mode,
fun_mode:fun_mode,
is_first_connection:true,
discord:discord,
FastAPI:FastAPI,
customLanguage: '', // Holds the value of the custom language input
selectedLanguage: '',
isLanguageMenuVisible: false,
rebooting_audio: new Audio("rebooting.wav"),
connection_lost_audio: new Audio("connection_lost.wav"),
connection_recovered_audio: new Audio("connection_recovered.wav"),
database_selectorDialogVisible:false,
progress_visibility:false,
progress_value:0,
codeBlockStylesheet:'',
sunIcon: document.querySelector(".sun"),
moonIcon: document.querySelector(".moon"),
userTheme: localStorage.getItem("theme"),
systemTheme: window.matchMedia("prefers-color-scheme: dark").matches,
lastMessageHtml:"",
defaultMessageHtml: `
<!DOCTYPE html>
@ -768,7 +1049,165 @@ export default {
methods: {
toggleMenu() {
this.isMenuVisible = !this.isMenuVisible;
if (this.isMenuVisible){
this.isinfosMenuVisible=false;
}
nextTick(() => {
feather.replace()
})
},
toggleInfosMenu() {
this.isinfosMenuVisible = !this.isinfosMenuVisible;
if (this.isinfosMenuVisible){
this.isMenuVisible=false;
}
nextTick(() => {
feather.replace()
})
},
adjustMenuPosition() {
const menu = this.$refs.languageMenu;
if(menu){
const rect = menu.getBoundingClientRect();
const windowWidth = window.innerWidth;
if (rect.right > windowWidth) {
menu.style.left = 'auto';
menu.style.right = '0';
} else {
menu.style.left = '0';
menu.style.right = 'auto';
}
}
},
addCustomLanguage() {
if (this.customLanguage.trim() !== '') {
this.selectLanguage(this.customLanguage);
this.customLanguage = ''; // Reset the input field after adding
}
},
async selectLanguage(language) {
await this.$store.dispatch('changeLanguage', language);
this.toggleLanguageMenu(); // Fermer le menu après le changement de langue
this.language = language
},
async deleteLanguage(language) {
await this.$store.dispatch('deleteLanguage', language);
this.toggleLanguageMenu(); // Fermer le menu après le changement de langue
this.language = language
},
toggleLanguageMenu() {
console.log("Toggling language ",this.isLanguageMenuVisible)
this.isLanguageMenuVisible = !this.isLanguageMenuVisible;
},
restartProgram(event) {
event.preventDefault();
this.$store.state.api_post_req('restart_program', this.$store.state.client_id)
this.rebooting_audio.play()
this.$store.state.toast.showToast("Rebooting the app. Please wait...", 410, false)
//this.$store.state.toast.showToast("Rebooting the app. Please wait...", 50, true);
console.log("this.$store.state.api_get_req",this.$store.state.api_get_req)
setTimeout(()=>{
window.close();
},2000)
},
applyConfiguration() {
this.isLoading = true;
console.log(this.$store.state.config)
axios.post('/apply_settings', {"client_id":this.$store.state.client_id, "config":this.$store.state.config}, {headers: this.posts_headers}).then((res) => {
this.isLoading = false;
//console.log('apply-res',res)
if (res.data.status) {
this.$store.state.toast.showToast("Configuration changed successfully.", 4, true)
this.settingsChanged = false
//this.save_configuration()
} else {
this.$store.state.toast.showToast("Configuration change failed.", 4, false)
}
nextTick(() => {
feather.replace()
})
})
},
fun_mode_on(){
console.log("Turning on fun mode")
this.$store.state.config.fun_mode=true;
this.applyConfiguration()
},
fun_mode_off(){
console.log("Turning off fun mode")
this.$store.state.config.fun_mode=false;
this.applyConfiguration()
},
showNews(){
this.$store.state.news.show()
},
themeCheck() {
if (this.userTheme == "dark" || (!this.userTheme && this.systemTheme)) {
document.documentElement.classList.add("dark");
this.moonIcon.classList.add("display-none");
nextTick(()=>{
//import('highlight.js/styles/tokyo-night-dark.css');
import('highlight.js/styles/stackoverflow-dark.css');
})
return
}
nextTick(()=>{
//import('highlight.js/styles/tomorrow-night-blue.css');
import('highlight.js/styles/stackoverflow-light.css');
})
this.sunIcon.classList.add("display-none")
},
themeSwitch() {
if (document.documentElement.classList.contains("dark")) {
document.documentElement.classList.remove("dark");
localStorage.setItem("theme", "light")
this.userTheme == "light"
this.iconToggle()
return
}
import('highlight.js/styles/tokyo-night-dark.css');
document.documentElement.classList.add("dark");
localStorage.setItem("theme", "dark")
this.userTheme == "dark"
this.iconToggle()
// Dispatch the themeChanged event
window.dispatchEvent(new Event('themeChanged'));
},
iconToggle() {
this.sunIcon.classList.toggle("display-none");
this.moonIcon.classList.toggle("display-none");
},
refreshPage() {
const hostnameParts = window.location.href.split('/');
if(hostnameParts.length > 4){
window.location.href='/'
}
else{
window.location.reload(true);
}
},
handleOk(inputText) {
console.log("Input text:", inputText);
},
getRandomEdgePosition() {
const edge = Math.floor(Math.random() * 4);
switch (edge) {
@ -2528,7 +2967,31 @@ export default {
socket.disconnect();
};
},
beforeUnmount() {
window.removeEventListener('resize', this.adjustMenuPosition);
},
async mounted() {
this.$store.state.toast = this.$refs.toast
this.$store.state.news = this.$refs.news
this.$store.state.messageBox = this.$refs.messageBox
this.$store.state.universalForm = this.$refs.universalForm
this.$store.state.yesNoDialog = this.$refs.yesNoDialog
this.$store.state.personality_editor = this.$refs.personality_editor
this.sunIcon = document.querySelector(".sun");
this.moonIcon = document.querySelector(".moon");
this.userTheme = localStorage.getItem("theme");
this.systemTheme = window.matchMedia("prefers-color-scheme: dark").matches;
this.themeCheck()
nextTick(() => {
feather.replace()
})
window.addEventListener('resize', this.adjustMenuPosition);
// let serverAddress = "http://localhost:9600/";
// try {
// const response = await fetch('/get_server_address'); // Replace with the actual endpoint on your Flask server
@ -2583,9 +3046,47 @@ export default {
ChoiceDialog,
ProgressBar,
InputBox,
SkillsLibraryViewer
SkillsLibraryViewer,
Toast,
MessageBox,
ProgressBar,
UniversalForm,
YesNoDialog,
PersonalityEditor,
PopupViewer,
ActionButton,
SocialIcon
},
watch: {
'$store.state.config.fun_mode': function(newVal, oldVal) {
console.log(`Fun mode changed from ${oldVal} to ${newVal}! 🎉`);
},
'$store.state.isConnected': function(newVal, oldVal) {
if (!this.isConnected){
this.$store.state.messageBox.showBlockingMessage("Server suddenly disconnected. Please reboot the server to recover the connection")
this.is_first_connection = false
console.log("this.is_first_connection set to false")
console.log(this.is_first_connection)
if(this.$store.state.config.activate_audio_infos)
this.connection_lost_audio.play()
}
else{
console.log("this.is_first_connection")
console.log(this.is_first_connection)
if(!this.is_first_connection){
this.$store.state.messageBox.hideMessage()
this.$store.state.messageBox.showMessage("Server connected.")
if(this.$store.state.config.activate_audio_infos)
this.connection_recovered_audio.play()
}
}
nextTick(() => {
feather.replace()
})
},
messages: {
handler: 'extractHtml',
deep: true
@ -2626,6 +3127,72 @@ export default {
},
computed: {
isFullMode() {
return this.$store.state.view_mode === 'full'; // Accessing the mode directly
},
storeLogo(){
if (this.$store.state.config){
return storeLogo
}
return this.$store.state.config.app_custom_logo!=''?'/user_infos/'+this.$store.state.config.app_custom_logo:storeLogo
},
languages: {
get(){
console.log("searching languages", this.$store.state.languages)
return this.$store.state.languages
}
},
language: {
get(){
console.log("searching language", this.$store.state.language)
return this.$store.state.language
}
},
currentPersonConfig (){
try{
return this.$store.state.currentPersonConfig
}
catch{
console.log("Error finding current personality configuration")
return undefined
}
},
selectedPersonality (){
try{
return this.$store.state.selectedPersonality
}
catch{
console.log("Error finding current personality configuration")
return undefined
}
},
loading_infos(){
return this.$store.state.loading_infos;
},
is_fun_mode(){
try{
if (this.$store.state.config){
return this.$store.state.config.fun_mode;
}
else{
return false;
}
}
catch(error){
console.error("Oopsie! Looks like we hit a snag: ", error);
return false;
}
},
isModelOK(){
return this.$store.state.isModelOk;
},
isGenerating(){
return this.$store.state.isGenerating;
},
isConnected(){
return this.$store.state.isConnected;
},
...mapState({
versionId: state => state.versionId,
}),