upgraded ui:
Added personalities zoo page enhanced Apps zoo page
@ -3,7 +3,12 @@ class WebAppLocalizer {
|
||||
this.translations = translations;
|
||||
this.localStoragePrefix = localStoragePrefix;
|
||||
this.currentLang = this.loadCurrentLanguage() || Object.keys(translations)[0];
|
||||
this.languageSelector = languageSelector;
|
||||
|
||||
if (typeof languageSelector === 'string') {
|
||||
this.languageSelector = document.getElementById(languageSelector);
|
||||
} else {
|
||||
this.languageSelector = languageSelector;
|
||||
}
|
||||
|
||||
if (this.languageSelector) {
|
||||
this.initializeLanguageSelector();
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 62fff368de81efeeee01c8ed2aa0bd973be26ab2
|
||||
Subproject commit 92109353b8e11d7d35a95c0640ca155f8d818cc7
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 499 B After Width: | Height: | Size: 499 B |
8
web/dist/assets/index-057fde26.css
vendored
8
web/dist/assets/index-cb896237.css
vendored
Normal file
Before Width: | Height: | Size: 224 B After Width: | Height: | Size: 224 B |
Before Width: | Height: | Size: 363 B After Width: | Height: | Size: 363 B |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 715 B After Width: | Height: | Size: 715 B |
4
web/dist/index.html
vendored
@ -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-b4757eb4.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index-057fde26.css">
|
||||
<script type="module" crossorigin src="/assets/index-4dce9705.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index-cb896237.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div ref="container"></div>
|
||||
<div :id="containerId" ref="container"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -8,54 +8,60 @@ export default {
|
||||
props: {
|
||||
ui: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: "",
|
||||
required: true
|
||||
},
|
||||
instanceId: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.renderUI();
|
||||
data() {
|
||||
return {
|
||||
containerId: `dynamic-ui-${this.instanceId}`
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
ui: {
|
||||
handler: 'renderUI',
|
||||
immediate: true,
|
||||
},
|
||||
handler(newValue) {
|
||||
console.log(`UI prop changed for instance ${this.instanceId}:`, newValue);
|
||||
this.$nextTick(() => {
|
||||
this.renderContent();
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
renderUI() {
|
||||
renderContent() {
|
||||
console.log(`Rendering content for instance ${this.instanceId}...`);
|
||||
const container = this.$refs.container;
|
||||
container.innerHTML = '';
|
||||
|
||||
|
||||
// Parse the UI string
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(this.ui, 'text/html');
|
||||
|
||||
// Extract and apply styles
|
||||
|
||||
// Extract and inject CSS
|
||||
const styles = doc.getElementsByTagName('style');
|
||||
Array.from(styles).forEach(style => {
|
||||
const styleElement = document.createElement('style');
|
||||
styleElement.textContent = style.textContent;
|
||||
container.appendChild(styleElement);
|
||||
const scopedStyle = document.createElement('style');
|
||||
scopedStyle.textContent = this.scopeCSS(style.textContent);
|
||||
document.head.appendChild(scopedStyle);
|
||||
});
|
||||
|
||||
// Extract and execute scripts
|
||||
|
||||
// Extract and inject HTML
|
||||
container.innerHTML = doc.body.innerHTML;
|
||||
|
||||
// Extract and execute JavaScript
|
||||
const scripts = doc.getElementsByTagName('script');
|
||||
Array.from(scripts).forEach(script => {
|
||||
const scriptElement = document.createElement('script');
|
||||
scriptElement.textContent = script.textContent;
|
||||
container.appendChild(scriptElement);
|
||||
const newScript = document.createElement('script');
|
||||
newScript.textContent = script.textContent;
|
||||
container.appendChild(newScript);
|
||||
});
|
||||
|
||||
// Append body content
|
||||
const body = doc.body;
|
||||
Array.from(body.children).forEach(child => {
|
||||
container.appendChild(child);
|
||||
});
|
||||
|
||||
// Apply any inline styles from the body
|
||||
if (body.style.cssText) {
|
||||
container.style.cssText = body.style.cssText;
|
||||
}
|
||||
},
|
||||
},
|
||||
scopeCSS(css) {
|
||||
return css.replace(/([^\r\n,{}]+)(,(?=[^}]*{)|\s*{)/g, `#${this.containerId} $1$2`);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
@ -94,13 +94,12 @@
|
||||
<JsonViewer :jsonFormText="metadata.title" :jsonData="metadata.content" :key="'msgjson-' + message.id" />
|
||||
</div>
|
||||
</div>
|
||||
<DynamicUIRenderer ref="ui" class="w-full" :ui="message.ui" :key="'msgui-' + message.id" />
|
||||
<DynamicUIRenderer v-if="message.ui" ref="ui" class="w-full" :ui="message.ui" :key="'msgui-' + message.id" />
|
||||
|
||||
<audio controls v-if="audio_url!=null" :key="audio_url">
|
||||
<source :src="audio_url" type="audio/wav" ref="audio_player" >
|
||||
Your browser does not support the audio element.
|
||||
</audio>
|
||||
|
||||
</audio>
|
||||
</div>
|
||||
<!-- MESSAGE CONTROLS -->
|
||||
<div class="flex-row justify-end mx-2">
|
||||
|
@ -28,6 +28,7 @@ const menuItems = ref([]);
|
||||
const navLinks = [
|
||||
{ active: true, route: 'discussions', text: 'Discussions' },
|
||||
{ active: true, route: 'playground', text: 'Playground' },
|
||||
{ active: true, route: 'PersonalitiesZoo', text: 'Personalities Zoo' },
|
||||
{ active: true, route: 'AppsZoo', text: 'Apps Zoo' },
|
||||
{
|
||||
active: false,
|
||||
|
@ -1,109 +1,62 @@
|
||||
<template>
|
||||
<div
|
||||
class=" min-w-96 items-start p-4 hover:bg-primary-light rounded-lg mb-2 shadow-lg border-2 cursor-pointer select-none"
|
||||
tabindex="-1"
|
||||
:class="selected_computed ? 'border-2 border-primary-light' : 'border-transparent', isMounted ? 'bg-blue-200 dark:bg-blue-700':''"
|
||||
<div class="personality-card bg-white border rounded-xl shadow-lg p-6 hover:shadow-xl transition duration-300 ease-in-out flex flex-col h-full"
|
||||
:class="selected_computed ? 'border-primary-light' : 'border-transparent', isMounted ? 'bg-blue-200 dark:bg-blue-700' : ''"
|
||||
:title="!personality.installed ? 'Not installed' : ''">
|
||||
<div class="flex-grow">
|
||||
<div class="flex items-center mb-4">
|
||||
<img :src="getImgUrl()" @error="defaultImg($event)" alt="Personality Icon"
|
||||
class="w-16 h-16 rounded-full border border-gray-300 mr-4 cursor-pointer"
|
||||
@click="toggleSelected"
|
||||
@mouseover="showThumbnail" @mousemove="updateThumbnailPosition" @mouseleave="hideThumbnail" />
|
||||
<div>
|
||||
<h3 class="font-bold text-xl text-gray-800 cursor-pointer" @click="toggleSelected">{{ personality.name }}</h3>
|
||||
<p class="text-sm text-gray-600">Author: {{ personality.author }}</p>
|
||||
<p class="text-sm text-gray-600">Version: {{ personality.version }}</p>
|
||||
<p v-if="personality.language" class="text-sm text-gray-600">Language: {{ personality.language }}</p>
|
||||
<p class="text-sm text-gray-600">Category: {{ personality.category }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div :class="!personality.installed ? 'border-red-500' : ''">
|
||||
<div class="mb-4">
|
||||
<h4 class="font-semibold mb-1 text-gray-700">Description:</h4>
|
||||
<p class="text-sm text-gray-600 h-20 overflow-y-auto" v-html="personality.description"></p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row items-center flex-shrink-0 gap-3">
|
||||
<img @click="toggleSelected" ref="imgElement" :src="getImgUrl()" @error="defaultImg($event)"
|
||||
class="w-10 h-10 rounded-full object-fill text-red-700 cursor-pointer"
|
||||
@mouseover="showThumbnail" @mousemove="updateThumbnailPosition" @mouseleave="hideThumbnail">
|
||||
<h3 @click="toggleSelected" class="font-bold font-large text-lg line-clamp-3 cursor-pointer"
|
||||
@mouseover="showThumbnail" @mousemove="updateThumbnailPosition" @mouseleave="hideThumbnail">
|
||||
{{ personality.name }}
|
||||
</h3>
|
||||
<button
|
||||
class="hover:text-secondary duration-75 active:scale-90 font-medium rounded-lg text-sm p-2 text-center inline-flex items-center"
|
||||
title="Copy link to clipboard" @click.stop="toggleCopyLink()">
|
||||
<i data-feather="clipboard" class="w-5"></i>
|
||||
<div v-if="personality.languages && select_language" class="mb-4">
|
||||
<label for="languages" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Select Language:</label>
|
||||
<select v-if="!isMounted" id="languages" v-model="personality.language"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
|
||||
<option v-for="(item, index) in personality.languages" :key="index" :selected="item == personality.languages[0]">
|
||||
{{ item }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-auto pt-4 border-t">
|
||||
<div class="flex justify-between items-center flex-wrap">
|
||||
<button @click="toggleFavorite" class="text-yellow-500 hover:text-yellow-600 transition duration-300 ease-in-out" :title="isFavorite ? 'Remove from favorites' : 'Add to favorites'">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" :fill="isFavorite ? 'currentColor' : 'none'" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" />
|
||||
</svg>
|
||||
</button>
|
||||
<div v-if="thumbnailVisible" :style="{ top: thumbnailPosition.y + 'px', left: thumbnailPosition.x + 'px' }"
|
||||
class="fixed z-50 w-20 h-20 rounded-full overflow-hidden">
|
||||
<img :src="getImgUrl()" class="w-full h-full object-fill">
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<div class="">
|
||||
|
||||
<div class="flex items-center">
|
||||
<i data-feather="user" class="w-5 m-1"></i>
|
||||
<b>Author: </b>
|
||||
|
||||
{{ personality.author }}
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<i data-feather="git-commit" class="w-5 m-1"></i>
|
||||
<b>Version: </b>
|
||||
|
||||
{{ personality.version }}
|
||||
</div>
|
||||
|
||||
<div v-if="personality.languages && select_language" class="flex items-center">
|
||||
<i data-feather="globe" class="w-5 m-1"></i>
|
||||
<b>Languages: </b>
|
||||
<select v-if="!isMounted" id="languages" v-model ="personality.language"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
|
||||
|
||||
<option v-for="(item, index) in personality.languages" :key="index"
|
||||
:selected="item == personality.languages[0]">{{
|
||||
item
|
||||
}}
|
||||
|
||||
</option>
|
||||
|
||||
</select>
|
||||
</div>
|
||||
<div v-if="personality.language" class="flex items-center">
|
||||
<i data-feather="globe" class="w-5 m-1"></i>
|
||||
<b>Language: </b>
|
||||
{{ personality.language }}
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<i data-feather="bookmark" class="w-5 m-1"></i>
|
||||
<b>Category: </b>
|
||||
|
||||
{{ personality.category }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<i data-feather="info" class="w-5 m-1"></i>
|
||||
<b>Description: </b><br>
|
||||
</div>
|
||||
<p class="mx-1 opacity-80 h-20 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" :title="personality.description" v-html="personality.description"></p>
|
||||
</div>
|
||||
<div class="rounded bg-blue-300">
|
||||
<button v-if="isMounted" type="button" title="Select"
|
||||
@click="toggleSelected"
|
||||
class="hover:text-secondary duration-75 active:scale-90 font-medium rounded-lg text-sm p-2 text-center inline-flex items-center " @click.stop="">
|
||||
<i
|
||||
data-feather="check"
|
||||
></i>
|
||||
<span class="sr-only">Select</span>
|
||||
</button>
|
||||
<button v-if="isMounted" type="button" title="Talk"
|
||||
@click="toggleTalk"
|
||||
class="hover:text-secondary duration-75 active:scale-90 font-medium rounded-lg text-sm p-2 text-center inline-flex items-center " @click.stop="">
|
||||
<i data-feather="send" class="w-5"></i>
|
||||
<span class="sr-only">Talk</span>
|
||||
</button>
|
||||
<button type="button" title="Show_folder"
|
||||
@click="showFolder"
|
||||
class="hover:text-secondary duration-75 active:scale-90 font-medium rounded-lg text-sm p-2 text-center inline-flex items-center " @click.stop="">
|
||||
<i data-feather="folder" class="w-5"></i>
|
||||
<span class="sr-only">Show Folder</span>
|
||||
</button>
|
||||
|
||||
|
||||
<InteractiveMenu :commands="commandsList" :force_position=2 title="Menu">
|
||||
|
||||
<button v-if="isMounted" @click="toggleSelected" class="text-blue-500 hover:text-blue-600 transition duration-300 ease-in-out" title="Select">
|
||||
<i data-feather="check" class="h-6 w-6"></i>
|
||||
</button>
|
||||
<button v-if="isMounted" @click="toggleTalk" class="text-green-500 hover:text-green-600 transition duration-300 ease-in-out" title="Talk">
|
||||
<i data-feather="send" class="h-6 w-6"></i>
|
||||
</button>
|
||||
<button @click="showFolder" class="text-purple-500 hover:text-purple-600 transition duration-300 ease-in-out" title="Show Folder">
|
||||
<i data-feather="folder" class="h-6 w-6"></i>
|
||||
</button>
|
||||
<InteractiveMenu :commands="commandsList" :force_position="2" title="Menu" class="text-gray-500 hover:text-gray-600 transition duration-300 ease-in-out">
|
||||
</InteractiveMenu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div v-if="thumbnailVisible" :style="{ top: thumbnailPosition.y + 'px', left: thumbnailPosition.x + 'px' }"
|
||||
class="fixed z-50 w-20 h-20 rounded-full overflow-hidden">
|
||||
<img :src="getImgUrl()" class="w-full h-full object-fill">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -11,6 +11,7 @@ import NodesView from '../views/NodesView.vue'
|
||||
import ComfyuiView from '../views/ComfyuiView.vue'
|
||||
import AutoSDView from '../views/AutoSDView.vue'
|
||||
import AppsZoo from '../views/AppsZoo.vue'
|
||||
import PersonalitiesZoo from '../views/PersonalitiesZoo.vue'
|
||||
|
||||
|
||||
const router = createRouter({
|
||||
@ -21,6 +22,11 @@ const router = createRouter({
|
||||
name: 'AppsZoo',
|
||||
component: AppsZoo
|
||||
},
|
||||
{
|
||||
path: '/personalities_view/',
|
||||
name: 'PersonalitiesZoo',
|
||||
component: PersonalitiesZoo
|
||||
},
|
||||
{
|
||||
path: '/auto_sd_view/',
|
||||
name: 'AutoSD',
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="app-zoo background-color w-full p-6 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">
|
||||
<nav class="panels-color shadow-lg rounded-lg p-4 max-w-4xl mx-auto">
|
||||
<nav class="panels-color shadow-lg rounded-lg p-4 max-w-4xl mx-auto mb-8">
|
||||
<div class="flex flex-wrap items-center justify-between gap-4">
|
||||
<div class="flex items-center space-x-4">
|
||||
<button
|
||||
@ -43,111 +43,83 @@
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-4">
|
||||
<label for="category-select" class="font-semibold">Category:</label>
|
||||
<select
|
||||
id="category-select"
|
||||
v-model="selectedCategory"
|
||||
class="border-2 border-gray-300 rounded-md px-2 py-1"
|
||||
>
|
||||
<option value="all">All Categories</option>
|
||||
<option v-for="category in categories" :key="category" :value="category">
|
||||
{{ category }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-4">
|
||||
<label for="sort-select" class="font-semibold">Sort by:</label>
|
||||
<select
|
||||
id="sort-select"
|
||||
v-model="sortBy"
|
||||
class="border-2 border-gray-300 rounded-md px-2 py-1"
|
||||
>
|
||||
<option value="name">Name</option>
|
||||
<option value="author">Author</option>
|
||||
<option value="date">Creation Date</option>
|
||||
<option value="update">Last Update</option>
|
||||
</select>
|
||||
<button @click="toggleSortOrder" class="btn btn-secondary">
|
||||
{{ sortOrder === 'asc' ? '↑' : '↓' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</nav>
|
||||
<div v-if="loading" class="flex justify-center items-center space-x-2 my-8" aria-live="polite">
|
||||
<div class="animate-spin rounded-full h-10 w-10 border-t-2 border-b-2 border-blue-500"></div>
|
||||
<span class="text-xl text-gray-700 font-semibold">Loading...</span>
|
||||
</div>
|
||||
<div v-for="category in categories" :key="category" class="mb-12">
|
||||
<h2 class="text-3xl font-bold mb-6 text-gray-800">{{ category }}</h2>
|
||||
</div>
|
||||
<div v-else>
|
||||
<h2 class="text-2xl font-bold mb-4">Favorite Apps</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mb-8">
|
||||
<app-card
|
||||
v-for="app in favoriteApps"
|
||||
:key="app.uid"
|
||||
:app="app"
|
||||
@toggle-favorite="toggleFavorite"
|
||||
@install="installApp"
|
||||
@uninstall="uninstallApp"
|
||||
@delete="deleteApp"
|
||||
@edit="editApp"
|
||||
@download="downloadApp"
|
||||
@view="handleAppClick"
|
||||
@open="openApp"
|
||||
@start-server="startServer"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h2 class="text-2xl font-bold mb-4">{{ currentCategoryName }}</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
<div
|
||||
v-for="app in filteredApps.filter(a => a.category === category)"
|
||||
:key="app.uid"
|
||||
class="app-card bg-white border rounded-xl shadow-lg p-6 hover:shadow-xl transition duration-300 ease-in-out flex flex-col h-full"
|
||||
>
|
||||
<div class="flex-grow">
|
||||
<div class="flex items-center mb-4">
|
||||
<img :src="app.icon" alt="App Icon" class="w-16 h-16 rounded-full border border-gray-300 mr-4" />
|
||||
<div>
|
||||
<h3 class="font-bold text-xl text-gray-800">{{ app.name }}</h3>
|
||||
<p class="text-sm text-gray-600">Author: {{ app.author }}</p>
|
||||
<p class="text-sm text-gray-600">Version: {{ app.version }}</p>
|
||||
<p class="text-sm text-gray-600">Creation date: {{ app.creation_date }}</p>
|
||||
<p class="text-sm text-gray-600">Last update date: {{ app.last_update_date }}</p>
|
||||
<!-- New section for app visibility -->
|
||||
<p class="text-sm" :class="app.is_public ? 'text-green-600' : 'text-orange-600'">
|
||||
{{ app.is_public ? 'Public App' : 'Local App' }}
|
||||
</p>
|
||||
<app-card
|
||||
v-for="app in sortedAndFilteredApps"
|
||||
:key="app.uid"
|
||||
:app="app"
|
||||
@toggle-favorite="toggleFavorite"
|
||||
@install="installApp"
|
||||
@uninstall="uninstallApp"
|
||||
@delete="deleteApp"
|
||||
@edit="editApp"
|
||||
@download="downloadApp"
|
||||
@view="handleAppClick"
|
||||
@open="openApp"
|
||||
@start-server="startServer"
|
||||
/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h4 class="font-semibold mb-1 text-gray-700">Description:</h4>
|
||||
<p class="text-sm text-gray-600 h-20 overflow-y-auto">{{ app.description }}</p>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-gray-600 mb-2">AI Model: {{ app.model_name }}</p>
|
||||
|
||||
<div v-if="app.disclaimer && app.disclaimer.trim() !== ''" class="mb-4">
|
||||
<h4 class="font-semibold mb-1 text-gray-700">Disclaimer:</h4>
|
||||
<p class="text-xs text-gray-500 italic h-16 overflow-y-auto">{{ app.disclaimer }}</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-auto pt-4 border-t">
|
||||
<div class="flex justify-between items-center flex-wrap">
|
||||
<button v-if="app.installed" @click="uninstallApp(app.folder_name)" class="text-red-500 hover:text-red-600 transition duration-300 ease-in-out" title="Uninstall">
|
||||
<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="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
<button v-else-if="app.existsInFolder" @click="deleteApp(app.name)" class="text-yellow-500 hover:text-yellow-600 transition duration-300 ease-in-out" title="Delete">
|
||||
<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="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
<button v-else @click="installApp(app.folder_name)" class="text-blue-500 hover:text-blue-600 transition duration-300 ease-in-out" title="Install">
|
||||
<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="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
||||
</svg>
|
||||
</button>
|
||||
<button v-if="app.installed" @click="editApp(app)" class="text-purple-500 hover:text-purple-600 transition duration-300 ease-in-out" title="Edit">
|
||||
<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="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button @click="downloadApp(app.folder_name)" class="text-green-500 hover:text-green-600 transition duration-300 ease-in-out" title="Download">
|
||||
<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="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
||||
</svg>
|
||||
</button>
|
||||
<button @click="handleAppClick(app)" class="text-gray-500 hover:text-gray-600 transition duration-300 ease-in-out" title="View">
|
||||
<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="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button v-if="app.installed" @click="openApp(app)" class="text-indigo-500 hover:text-indigo-600 transition duration-300 ease-in-out" title="Open">
|
||||
<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="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- New button for starting the server -->
|
||||
<button v-if="app.has_server && app.installed" @click="startServer(app.folder_name)" class="text-teal-500 hover:text-teal-600 transition duration-300 ease-in-out" title="Start Server">
|
||||
<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 12h14M12 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- New button for updating the app -->
|
||||
<button v-if="app.has_update" @click="installApp(app.folder_name)" class="relative text-yellow-500 hover:text-yellow-600 transition duration-300 ease-in-out animate-pulse" title="Update Available">
|
||||
<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="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" />
|
||||
</svg>
|
||||
<span class="absolute top-0 right-0 inline-flex items-center justify-center px-2 py-1 text-xs font-bold leading-none text-red-100 transform translate-x-1/2 -translate-y-1/2 bg-red-600 rounded-full">!</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- App details modal -->
|
||||
<div v-if="selectedApp" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div class="bg-white rounded-lg p-6 w-11/12 h-5/6 flex flex-col">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
@ -159,6 +131,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast message -->
|
||||
<div v-if="message" class="fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-md" :class="{ 'bg-green-100 text-green-800': successMessage, 'bg-red-100 text-red-800': !successMessage }">
|
||||
{{ message }}
|
||||
</div>
|
||||
@ -167,12 +140,18 @@
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import AppCard from '@/components/AppCard.vue'; // Create this component for individual app cards
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AppCard,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
apps: [],
|
||||
githubApps: [],
|
||||
favorites: [],
|
||||
selectedCategory: 'all',
|
||||
selectedApp: null,
|
||||
appCode: '',
|
||||
loading: false,
|
||||
@ -181,11 +160,15 @@ export default {
|
||||
searchQuery: '',
|
||||
selectedFile: null,
|
||||
isUploading: false,
|
||||
message: '',
|
||||
error: ''
|
||||
error: '',
|
||||
sortBy: 'name',
|
||||
sortOrder: 'asc',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
currentCategoryName() {
|
||||
return this.selectedCategory === 'all' ? 'All Apps' : this.selectedCategory;
|
||||
},
|
||||
combinedApps() {
|
||||
const installedAppNames = this.apps.map(app => app.name);
|
||||
const localAppsMap = new Map(this.apps.map(app => [app.name, { ...app, installed: true, existsInFolder: true }]));
|
||||
@ -198,17 +181,66 @@ export default {
|
||||
|
||||
return Array.from(localAppsMap.values());
|
||||
},
|
||||
filteredApps() {
|
||||
return this.combinedApps.filter(app =>
|
||||
app.name.toLowerCase().includes(this.searchQuery.toLowerCase()) ||
|
||||
app.description.toLowerCase().includes(this.searchQuery.toLowerCase())
|
||||
);
|
||||
},
|
||||
categories() {
|
||||
return [...new Set(this.combinedApps.map(app => app.category))];
|
||||
}
|
||||
},
|
||||
filteredApps() {
|
||||
return this.combinedApps.filter(app => {
|
||||
const matchesSearch = app.name.toLowerCase().includes(this.searchQuery.toLowerCase()) ||
|
||||
app.description.toLowerCase().includes(this.searchQuery.toLowerCase()) ||
|
||||
app.author.toLowerCase().includes(this.searchQuery.toLowerCase());
|
||||
const matchesCategory = this.selectedCategory === 'all' || app.category === this.selectedCategory;
|
||||
return matchesSearch && matchesCategory;
|
||||
});
|
||||
},
|
||||
sortedAndFilteredApps() {
|
||||
return this.filteredApps.sort((a, b) => {
|
||||
let comparison = 0;
|
||||
switch (this.sortBy) {
|
||||
case 'name':
|
||||
comparison = a.name.localeCompare(b.name);
|
||||
break;
|
||||
case 'author':
|
||||
comparison = a.author.localeCompare(b.author);
|
||||
break;
|
||||
case 'date':
|
||||
comparison = new Date(a.creation_date) - new Date(b.creation_date);
|
||||
break;
|
||||
case 'update':
|
||||
comparison = new Date(a.last_update_date) - new Date(b.last_update_date);
|
||||
break;
|
||||
}
|
||||
return this.sortOrder === 'asc' ? comparison : -comparison;
|
||||
});
|
||||
},
|
||||
favoriteApps() {
|
||||
return this.combinedApps.filter(app => this.favorites.includes(app.uid));
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleSortOrder() {
|
||||
this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc';
|
||||
},
|
||||
toggleFavorite(appUid) {
|
||||
const index = this.favorites.indexOf(appUid);
|
||||
if (index === -1) {
|
||||
this.favorites.push(appUid);
|
||||
} else {
|
||||
this.favorites.splice(index, 1);
|
||||
}
|
||||
this.saveFavoritesToLocalStorage();
|
||||
},
|
||||
saveFavoritesToLocalStorage() {
|
||||
localStorage.setItem('appZooFavorites', JSON.stringify(this.favorites));
|
||||
},
|
||||
|
||||
loadFavoritesFromLocalStorage() {
|
||||
const savedFavorites = localStorage.getItem('appZooFavorites');
|
||||
console.log("savedFavorites",savedFavorites)
|
||||
if (savedFavorites) {
|
||||
this.favorites = JSON.parse(savedFavorites);
|
||||
}
|
||||
},
|
||||
startServer(appName) {
|
||||
const payload = {
|
||||
client_id: this.$store.state.client_id, // Assuming you have a clientId property in your component
|
||||
@ -450,6 +482,7 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.fetchGithubApps();
|
||||
this.loadFavoritesFromLocalStorage();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -4979,10 +4979,8 @@ export default {
|
||||
|
||||
console.log("category")
|
||||
|
||||
|
||||
//await this.getPersonalitiesArr()
|
||||
this.personality_category = this.configFile.personality_category
|
||||
this.personalitiesFiltered = this.personalities.filter((item) => item.category === this.configFile.personality_category)
|
||||
this.personalitiesFiltered = this.$store.state.personalities.filter((item) => item.category === this.configFile.personality_category)
|
||||
// this.personalitiesFiltered.sort()
|
||||
//mountedPersArr
|
||||
this.modelsFiltered = []
|
||||
@ -5716,7 +5714,7 @@ export default {
|
||||
this.api_get_req("list_personalities_categories").then((cats)=>{
|
||||
console.log("cats",cats)
|
||||
this.persCatgArr = cats
|
||||
this.personalitiesFiltered = this.personalities.filter((item) => item.category === this.personality_category)
|
||||
this.personalitiesFiltered = this.$store.state.personalities.filter((item) => item.category === this.personality_category)
|
||||
this.personalitiesFiltered.sort()
|
||||
|
||||
})
|
||||
@ -6038,11 +6036,10 @@ export default {
|
||||
},
|
||||
async getPersonalitiesArr() {
|
||||
this.isLoading = true
|
||||
this.personalities = []
|
||||
this.$store.state.personalities = []
|
||||
const dictionary = await this.api_get_req("get_all_personalities")
|
||||
const config = this.$store.state.config
|
||||
//console.log('asdas',config)
|
||||
// console.log("all_personalities")
|
||||
console.log("recovering all_personalities")
|
||||
// console.log(dictionary)
|
||||
const catkeys = Object.keys(dictionary); // returns categories folder names
|
||||
for (let j = 0; j < catkeys.length; j++) {
|
||||
@ -6064,15 +6061,15 @@ export default {
|
||||
})
|
||||
|
||||
|
||||
if (this.personalities.length == 0) {
|
||||
this.personalities = modPersArr
|
||||
if (this.$store.state.personalities.length == 0) {
|
||||
this.$store.state.personalities = modPersArr
|
||||
} else {
|
||||
this.personalities = this.personalities.concat(modPersArr)
|
||||
this.$store.state.personalities = this.$store.state.personalities.concat(modPersArr)
|
||||
}
|
||||
}
|
||||
|
||||
this.personalities.sort((a, b) => a.name.localeCompare(b.name))
|
||||
this.personalitiesFiltered = this.personalities.filter((item) => item.category === this.configFile.personality_category)
|
||||
this.$store.state.personalities.sort((a, b) => a.name.localeCompare(b.name))
|
||||
this.personalitiesFiltered = this.$store.state.personalities.filter((item) => item.category === this.configFile.personality_category)
|
||||
this.personalitiesFiltered.sort()
|
||||
console.log('per filtered', this.personalitiesFiltered)
|
||||
this.isLoading = false
|
||||
@ -6080,13 +6077,13 @@ export default {
|
||||
},
|
||||
async filterPersonalities() {
|
||||
if (!this.searchPersonality) {
|
||||
this.personalitiesFiltered = this.personalities.filter((item) => item.category === this.configFile.personality_category )
|
||||
this.personalitiesFiltered = this.$store.state.personalities.filter((item) => item.category === this.configFile.personality_category )
|
||||
this.personalitiesFiltered.sort()
|
||||
this.searchPersonalityInProgress = false
|
||||
return
|
||||
}
|
||||
const searchTerm = this.searchPersonality.toLowerCase()
|
||||
const seachedPersonalities = this.personalities.filter((item) => {
|
||||
const seachedPersonalities = this.$store.state.personalities.filter((item) => {
|
||||
|
||||
if (item.name && item.name.toLowerCase().includes(searchTerm) || item.description && item.description.toLowerCase().includes(searchTerm) || item.full_path && item.full_path.toLowerCase().includes(searchTerm)) {
|
||||
return item
|
||||
@ -6099,7 +6096,7 @@ export default {
|
||||
if (seachedPersonalities.length > 0) {
|
||||
this.personalitiesFiltered = seachedPersonalities.sort()
|
||||
} else {
|
||||
this.personalitiesFiltered = this.personalities.filter((item) => item.category === this.configFile.personality_category)
|
||||
this.personalitiesFiltered = this.$store.state.personalities.filter((item) => item.category === this.configFile.personality_category)
|
||||
this.personalitiesFiltered.sort()
|
||||
}
|
||||
this.searchPersonalityInProgress = false
|
||||
@ -6265,12 +6262,12 @@ export default {
|
||||
if (res.status) {
|
||||
this.configFile.personalities = res.personalities
|
||||
this.$store.state.toast.showToast("Personality unmounted", 4, true)
|
||||
const persId = this.personalities.findIndex(item => item.full_path == pers.full_path)
|
||||
const persId = this.$store.state.personalities.findIndex(item => item.full_path == pers.full_path)
|
||||
const persFilteredId = this.personalitiesFiltered.findIndex(item => item.full_path == pers.full_path)
|
||||
const persIdZoo = this.$refs.personalitiesZoo.findIndex(item => item.full_path == pers.full_path)
|
||||
console.log('ppp', this.personalities[persId])
|
||||
console.log('ppp', this.$store.state.personalities[persId])
|
||||
|
||||
this.personalities[persId].isMounted = false
|
||||
this.$store.state.personalities[persId].isMounted = false
|
||||
|
||||
if (persFilteredId > -1) {
|
||||
this.personalitiesFiltered[persFilteredId].isMounted = false
|
||||
@ -6866,9 +6863,9 @@ export default {
|
||||
if (!this.isMounted) {
|
||||
return null
|
||||
}
|
||||
const index = this.personalities.findIndex(item => item.full_path === this.configFile.personalities[this.configFile.active_personality_id])
|
||||
const index = this.$store.state.personalities.findIndex(item => item.full_path === this.configFile.personalities[this.configFile.active_personality_id])
|
||||
if (index > -1) {
|
||||
return this.personalities[index].name
|
||||
return this.$store.state.personalities[index].name
|
||||
} else {
|
||||
return null
|
||||
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit b1adf519ca590c1b1bdfce02bc2ae87a16fc869d
|
||||
Subproject commit 31a63be27ac43e6a344220f40aeab559394d8eb5
|