Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c89703531c | |||
| 77269fe4b4 | |||
| 89afded87e | |||
| 08a5ff700e | |||
| edf57900dc |
@@ -0,0 +1,34 @@
|
||||
from fastapi import FastAPI
|
||||
|
||||
class ThemeCustomizerPlugin:
|
||||
def __init__(self):
|
||||
self.name = "theme_customizer"
|
||||
self.description = "Theme customization plugin for OpenWebUI"
|
||||
self.version = "1.0.0"
|
||||
self.author = "SamiAhmed7777"
|
||||
|
||||
def setup(self) -> None:
|
||||
"""Setup any initial plugin state."""
|
||||
pass
|
||||
|
||||
def register(self, app: FastAPI) -> None:
|
||||
"""Register the theme customizer plugin with OpenWebUI."""
|
||||
# Register frontend components
|
||||
if not hasattr(app.state, "plugin_components"):
|
||||
app.state.plugin_components = {}
|
||||
|
||||
# Theme Customization Component
|
||||
app.state.plugin_components["theme_customizer"] = {
|
||||
"name": "ThemeCustomizer",
|
||||
"path": "/plugins/theme_customizer/components/ThemeCustomizer.svelte",
|
||||
"settings": {
|
||||
"section": "general", # This will make it appear under General
|
||||
"title": "Theme Customization",
|
||||
"icon": "palette",
|
||||
"description": "Customize colors, fonts, and appearance",
|
||||
"order": 300 # This will place it after the general settings
|
||||
}
|
||||
}
|
||||
|
||||
# Create plugin instance
|
||||
plugin = ThemeCustomizerPlugin()
|
||||
@@ -0,0 +1,109 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import Button from '$lib/components/ui/button/Button.svelte';
|
||||
import Input from '$lib/components/ui/input/Input.svelte';
|
||||
import Label from '$lib/components/ui/label/Label.svelte';
|
||||
import Card from '$lib/components/ui/card/Card.svelte';
|
||||
import Select from '$lib/components/ui/select/Select.svelte';
|
||||
import { toast } from '$lib/components/ui/toast';
|
||||
|
||||
let primaryColor = '#007AFF';
|
||||
let secondaryColor = '#6B7280';
|
||||
let backgroundColor = '#FFFFFF';
|
||||
let textColor = '#000000';
|
||||
let fontSize = '16px';
|
||||
let currentFont = 'Inter';
|
||||
|
||||
let availableFonts = [
|
||||
'Inter',
|
||||
'Roboto',
|
||||
'Open Sans',
|
||||
'Montserrat',
|
||||
'Lato'
|
||||
];
|
||||
|
||||
async function updateTheme() {
|
||||
try {
|
||||
document.documentElement.style.setProperty('--primary-color', primaryColor);
|
||||
document.documentElement.style.setProperty('--secondary-color', secondaryColor);
|
||||
document.documentElement.style.setProperty('--background-color', backgroundColor);
|
||||
document.documentElement.style.setProperty('--text-color', textColor);
|
||||
document.documentElement.style.setProperty('--font-size', fontSize);
|
||||
document.documentElement.style.setProperty('--font-family', currentFont);
|
||||
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'Theme updated successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Failed to update theme',
|
||||
variant: 'destructive'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
// Load current theme values from CSS variables
|
||||
primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim() || primaryColor;
|
||||
secondaryColor = getComputedStyle(document.documentElement).getPropertyValue('--secondary-color').trim() || secondaryColor;
|
||||
backgroundColor = getComputedStyle(document.documentElement).getPropertyValue('--background-color').trim() || backgroundColor;
|
||||
textColor = getComputedStyle(document.documentElement).getPropertyValue('--text-color').trim() || textColor;
|
||||
fontSize = getComputedStyle(document.documentElement).getPropertyValue('--font-size').trim() || fontSize;
|
||||
currentFont = getComputedStyle(document.documentElement).getPropertyValue('--font-family').trim() || currentFont;
|
||||
});
|
||||
</script>
|
||||
|
||||
<Card class="p-6">
|
||||
<h2 class="text-2xl font-bold mb-4">Theme Customization</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label>Primary Color</Label>
|
||||
<Input type="color" bind:value={primaryColor} />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label>Secondary Color</Label>
|
||||
<Input type="color" bind:value={secondaryColor} />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label>Background Color</Label>
|
||||
<Input type="color" bind:value={backgroundColor} />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label>Text Color</Label>
|
||||
<Input type="color" bind:value={textColor} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label>Font Size</Label>
|
||||
<Input
|
||||
type="text"
|
||||
bind:value={fontSize}
|
||||
placeholder="e.g., 16px, 1.2rem"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label>Font Family</Label>
|
||||
<Select
|
||||
bind:value={currentFont}
|
||||
options={availableFonts}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="primary"
|
||||
class="w-full mt-4"
|
||||
on:click={updateTheme}
|
||||
>
|
||||
Apply Theme
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 306 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 303 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 335 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 306 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 312 KiB |
@@ -0,0 +1,109 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import Button from '$lib/components/ui/button/Button.svelte';
|
||||
import Input from '$lib/components/ui/input/Input.svelte';
|
||||
import Label from '$lib/components/ui/label/Label.svelte';
|
||||
import Card from '$lib/components/ui/card/Card.svelte';
|
||||
import Select from '$lib/components/ui/select/Select.svelte';
|
||||
import { toast } from '$lib/components/ui/toast';
|
||||
|
||||
let primaryColor = '#007AFF';
|
||||
let secondaryColor = '#6B7280';
|
||||
let backgroundColor = '#FFFFFF';
|
||||
let textColor = '#000000';
|
||||
let fontSize = '16px';
|
||||
let currentFont = 'Inter';
|
||||
|
||||
let availableFonts = [
|
||||
'Inter',
|
||||
'Roboto',
|
||||
'Open Sans',
|
||||
'Montserrat',
|
||||
'Lato'
|
||||
];
|
||||
|
||||
async function updateTheme() {
|
||||
try {
|
||||
document.documentElement.style.setProperty('--primary-color', primaryColor);
|
||||
document.documentElement.style.setProperty('--secondary-color', secondaryColor);
|
||||
document.documentElement.style.setProperty('--background-color', backgroundColor);
|
||||
document.documentElement.style.setProperty('--text-color', textColor);
|
||||
document.documentElement.style.setProperty('--font-size', fontSize);
|
||||
document.documentElement.style.setProperty('--font-family', currentFont);
|
||||
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'Theme updated successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Failed to update theme',
|
||||
variant: 'destructive'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
// Load current theme values from CSS variables
|
||||
primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim() || primaryColor;
|
||||
secondaryColor = getComputedStyle(document.documentElement).getPropertyValue('--secondary-color').trim() || secondaryColor;
|
||||
backgroundColor = getComputedStyle(document.documentElement).getPropertyValue('--background-color').trim() || backgroundColor;
|
||||
textColor = getComputedStyle(document.documentElement).getPropertyValue('--text-color').trim() || textColor;
|
||||
fontSize = getComputedStyle(document.documentElement).getPropertyValue('--font-size').trim() || fontSize;
|
||||
currentFont = getComputedStyle(document.documentElement).getPropertyValue('--font-family').trim() || currentFont;
|
||||
});
|
||||
</script>
|
||||
|
||||
<Card class="p-6">
|
||||
<h2 class="text-2xl font-bold mb-4">Theme Customization</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label>Primary Color</Label>
|
||||
<Input type="color" bind:value={primaryColor} />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label>Secondary Color</Label>
|
||||
<Input type="color" bind:value={secondaryColor} />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label>Background Color</Label>
|
||||
<Input type="color" bind:value={backgroundColor} />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label>Text Color</Label>
|
||||
<Input type="color" bind:value={textColor} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label>Font Size</Label>
|
||||
<Input
|
||||
type="text"
|
||||
bind:value={fontSize}
|
||||
placeholder="e.g., 16px, 1.2rem"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label>Font Family</Label>
|
||||
<Select
|
||||
bind:value={currentFont}
|
||||
options={availableFonts}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="primary"
|
||||
class="w-full mt-4"
|
||||
on:click={updateTheme}
|
||||
>
|
||||
Apply Theme
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 306 KiB |
|
After Width: | Height: | Size: 303 KiB |
|
After Width: | Height: | Size: 335 KiB |
|
After Width: | Height: | Size: 306 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 335 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 306 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 409 KiB |
@@ -1,21 +1,21 @@
|
||||
{
|
||||
"name": "Open WebUI",
|
||||
"short_name": "WebUI",
|
||||
"name": "chat.sami",
|
||||
"short_name": "chat.sami",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/static/web-app-manifest-192x192.png",
|
||||
"src": "/web-app-manifest-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/static/web-app-manifest-512x512.png",
|
||||
"src": "/web-app-manifest-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"theme_color": "#076291",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 335 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 306 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 339 KiB |