💄 style: Add locales and improve devops

pull/472/head
canisminor1990 2023-11-23 23:25:08 +08:00
parent 361ae84698
commit a03f1b3bfb
83 changed files with 4130 additions and 3076 deletions

View File

@ -1,19 +1,8 @@
const config = require('@lobehub/lint').eslint;
config.rules['indent'] = ['off', 2];
config.rules['linebreak-style'] = 0;
config.rules['no-undef'] = 0;
config.rules['object-curly-spacing'] = 0;
config.rules['unicorn/prefer-add-event-listener'] = 0;
module.exports = {
...config,
overrides: [
{
files: ['*.ts', '*.tsx'],
rules: {
'linebreak-style': 0,
'no-undef': 0,
'object-curly-spacing': 0,
'unicorn/prefer-add-event-listener': 0,
'unused-imports/no-unused-imports': 0,
},
},
],
};
module.exports = config;

View File

@ -10,7 +10,6 @@ module.exports = {
entryLocale: 'en_US',
output: 'locales',
outputLocales: outputLocales,
temperature: 0,
modelName: 'gpt-3.5-turbo-1106',
experimental: {
jsonMode: true,

159
locales/de_DE.json Normal file
View File

@ -0,0 +1,159 @@
{
"brand": {
"kitchen": "Kitchen",
"lobe": "LobeHub",
"custom": "Benutzerdefiniert"
},
"custom": {
"initializing": "StableDiffusion / LobeTheme initialisiert, bitte warten..."
},
"footer": {
"resources": "Ressourcen",
"community": "Gemeinschaft",
"help": "Hilfe",
"moreProducts": "Weitere Produkte"
},
"header": {
"feedback": "Feedback",
"switchTheme": "Wechseln Sie das Licht-/Dunkel-Thema",
"setting": "Einstellung"
},
"modal": {
"themeFeedback": {
"title": "Themenfeedback"
},
"themeSetting": {
"title": "Themen-Einstellungen"
}
},
"prompt": {
"load": "Lade-Prompt",
"set": "Setze-Prompt",
"negative": "Negativ",
"positive": "Positiv"
},
"setting": {
"button": {
"reset": "Zurücksetzen",
"submit": "Anwenden und Schnittstelle neu starten"
},
"confirmPageUnload": {
"title": "Bestätigung beim Verlassen der Seite",
"desc": "Hilft, den Verlust von ungespeicherten Daten zu verhindern"
},
"customFont": {
"title": "Benutzerdefinierte Schriftart",
"desc": "Wenn aktiviert, wird automatisch eine Webfont geladen, um die Anzeige von Text in Chinesisch, Englisch und Code zu verbessern"
},
"customLogo": {
"title": "Benutzerdefiniertes Logo",
"desc": "Unterstützt URL / Base64 / Emoji-Symbole"
},
"customTitle": {
"title": "Benutzerdefinierter Titel",
"desc": "Benutzerdefinierter Logotitel"
},
"extraNetworkSidebar": {
"defaultCardSize": {
"title": "Modell-Cover-Größe",
"desc": "Standardwert der Modell-Cover-Größe beim Starten"
},
"defaultExpand": {
"title": "Standardmäßig erweitern",
"desc": "Ob die Seitenleiste beim Starten standardmäßig erweitert werden soll"
},
"defaultWidth": {
"title": "Standardbreite",
"desc": "Standardbreite der Seitenleiste beim Starten"
},
"displayMode": {
"title": "Anzeigemodus",
"desc": "Festgelegt als Rastermodus für konstante Anzeige, automatische Erweiterung, wenn die Maus an den Rand im schwebenden Modus bewegt wird"
},
"enable": {
"title": "Aktivieren",
"desc": "Aktivieren Sie die zusätzliche Netzwerkseitenleiste auf der rechten Seite"
}
},
"group": {
"extraNetworkSidebar": "Zusätzliche Netzwerkseitenleiste",
"layout": "Layout-Einstellungen",
"promptTextarea": "Prompt-Textfeld",
"quickSettingSidebar": "Schnelle Einstellungsseitenleiste",
"theme": "Themen-Einstellungen"
},
"hideFooter": {
"title": "Fußzeile ausblenden",
"desc": "Blenden Sie die Themenfußzeile aus und zeigen Sie nur die Standardfußzeile von Stable Diffusion WebUI an"
},
"language": {
"title": "Sprache",
"desc": "Lappen-Themensprache"
},
"logoType": {
"title": "Logo-Typ",
"desc": "Logo-Typ",
"preview": "Vorschau"
},
"neutralColor": {
"title": "Neutrale Farbe",
"desc": "Passen Sie verschiedene Grautöne mit unterschiedlichen Farbtendenzen an, der zweite ist die originale neutrale Farbe der Küche"
},
"primaryColor": {
"title": "Primärfarbe",
"desc": "Benutzerdefinierte Primärfarbe, die zweite ist die originale Farbe des Küchenthemas"
},
"promptDisplayMode": {
"title": "Prompt-Anzeigemodus",
"desc": "Feste Höhe oder automatische Höhe mit ziehbarer Größenänderungsunterstützung",
"resizable": "Größenänderbar",
"scroll": "Scrollen"
},
"promptEditor": {
"title": "Prompt-Editor",
"desc": "Bietet einen einfachen Prompt-Editor oben in der Schnelleinstellungsseitenleiste"
},
"promptHighlight": {
"title": "Prompt-Syntaxhervorhebung",
"desc": "Färbt die Prompt-Anzeige automatisch gemäß den Stable Diffusion-Syntaxregeln ein"
},
"quickSettingSidebar": {
"defaultExpand": {
"title": "Standardmäßig erweitern",
"desc": "Ob die Seitenleiste beim Starten standardmäßig erweitert werden soll"
},
"defaultWidth": {
"title": "Standardbreite",
"desc": "Standardbreite der Seitenleiste beim Starten"
},
"displayMode": {
"title": "Anzeigemodus",
"desc": "Festgelegt als Rastermodus für konstante Anzeige, automatische Erweiterung, wenn die Maus an den Rand im schwebenden Modus bewegt wird"
},
"enable": {
"title": "Aktivieren",
"desc": "Aktivieren Sie die Schnelleinstellungsseitenleiste auf der linken Seite"
}
},
"reduceAnimation": {
"title": "Animation reduzieren",
"desc": "Reduzieren Sie den Unschärfeeffekt und die Hintergrundflussfarbe, um die Geschmeidigkeit zu verbessern und die CPU-Auslastung zu reduzieren"
},
"splitPreviewer": {
"title": "Split-Previewer",
"desc": "Platzieren Sie das Prompt-Eingabefeld links und die Generierungsschaltfläche rechts, um sicherzustellen, dass das generierte Bild beim Scrollen immer oben angezeigt wird (experimentell)"
},
"svgIcons": {
"title": "SVG-Symbole",
"desc": "Ersetzen Sie alle Emoji-Symbole in Stable Diffusion WebUI global durch SVG-Symbole"
}
},
"sidebar": {
"extraNetwork": "Zusätzliches Netzwerk",
"quickSetting": "Schnelleinstellung",
"mode": {
"fixed": "Fixiert",
"float": "Schwebend"
}
}
}

View File

@ -1,82 +1,159 @@
{
"appInitializing": "StableDiffusion / LobeTheme is initializing, please wait...",
"community": "Community",
"custom": "Custom",
"extraNetwork": "Extra Network",
"feedback": "Feedback",
"fixed": "Fixed",
"float": "Float",
"help": "Help",
"kitchen": "Kitchen",
"loadPrompt": "Load Prompt",
"lobe": "Lobe",
"moreProducts": "More Products",
"negative": "Negative",
"positive": "Positive",
"quickSetting": "Quick Setting",
"resizable": "Resizable",
"resources": "Resources",
"scroll": "Scroll",
"setPrompt": "Set Prompt",
"setting": "Setting",
"settingButtonReset": "Reset",
"settingButtonSubmit": "Apply and Restart Interface",
"settingConfirmPageUnload": "Confirmation on page leaving",
"settingConfirmPageUnloadDesc": "Helps prevent loss of unsaved data",
"settingCustomFont": "Load Custom Font",
"settingCustomFontDesc": "When enabled, it will automatically load a webfont to enhance the display of text in Chinese, English, and code",
"settingCustomLogo": "Custom Logo",
"settingCustomLogoDesc": "Support URL / Base64 / Emoji symbols",
"settingCustomTitle": "Custom Title",
"settingCustomTitleDesc": "Custom Logo Title",
"settingExtraNetworkSidebarDefaultCardSize": "Model Cover Size",
"settingExtraNetworkSidebarDefaultCardSizeDesc": "Default value of model cover size when starting",
"settingExtraNetworkSidebarDefaultExpand": "Default Expand",
"settingExtraNetworkSidebarDefaultExpandDesc": "Whether to expand the sidebar by default when starting",
"settingExtraNetworkSidebarDefaultWidth": "Default Width",
"settingExtraNetworkSidebarDefaultWidthDesc": "Default width of the sidebar when starting",
"settingExtraNetworkSidebarDisplayMode": "Display Mode",
"settingExtraNetworkSidebarDisplayModeDesc": "Fixed as grid mode for constant display, auto-expand when the mouse moves to the side in floating mode",
"settingExtraNetworkSidebarEnable": "Enable",
"settingExtraNetworkSidebarEnableDesc": "Enable the extra network sidebar on the right side",
"settingGroupExtraNetworkSidebar": "Extra Network Sidebar",
"settingGroupLayout": "Layout Settings",
"settingGroupPromptTextarea": "Prompt Textbox",
"settingGroupQuickSettingSidebar": "Quick Setting Sidebar",
"settingGroupTheme": "Theme Settings",
"settingHideFooter": "Hide Footer",
"settingHideFooterDesc": "Hide the theme footer and only display the default footer of stable diffusion webui",
"settingLanguage": "Language",
"settingLanguageDesc": "Lobe Theme language",
"settingLogoPreview": "Preview",
"settingLogoType": "Logo Type",
"settingLogoTypeDesc": "Logo Type",
"settingNeutralColor": "Neutral Color",
"settingNeutralColorDesc": "Customize different shades of gray with different color tendencies, the second one is the original Kitchen neutral color",
"settingPrimaryColor": "Primary Color",
"settingPrimaryColorDesc": "Custom primary color, the second one is the original Kitchen theme color",
"settingPromptDisplayMode": "Display Mode",
"settingPromptDisplayModeDesc": "Fixed height or auto height with draggable resize support",
"settingPromptEditor": "Prompt Editor",
"settingPromptEditorDesc": "Provide a simple prompt editor at the top of the quick setting sidebar",
"settingPromptHighlight": "Prompt Syntax Highlighting",
"settingPromptHighlightDesc": "Automatically colorize prompt display according to the Stable Diffusion syntax rules",
"settingQuickSettingSidebarDefaultExpand": "Default Expand",
"settingQuickSettingSidebarDefaultExpandDesc": "Whether to expand the sidebar by default when starting",
"settingQuickSettingSidebarDefaultWidth": "Default Width",
"settingQuickSettingSidebarDefaultWidthDesc": "Default width of the sidebar when starting",
"settingQuickSettingSidebarDisplayMode": "Display Mode",
"settingQuickSettingSidebarDisplayModeDesc": "Fixed as grid mode for constant display, auto-expand when the mouse moves to the side in floating mode",
"settingQuickSettingSidebarEnable": "Enable",
"settingQuickSettingSidebarEnableDesc": "Enable the quick setting sidebar on the left side",
"settingReduceAnimation": "Reduce Animation",
"settingReduceAnimationDesc": "Reduce the blur effect and background flow color, which can improve smoothness and save CPU usage",
"settingSplitPreviewer": "Split Previewer",
"settingSplitPreviewerDesc": "Put the prompt input box on the left and the generate button on the right, ensuring that the generated image is always displayed at the top when scrolling (experimental)",
"settingSvgIcons": "Use SVG Icons",
"settingSvgIconsDesc": "Replace all Emoji icons in stable diffusion webui with SVG icons globally",
"switchTheme": "Switch Light/Dark Theme",
"sync": "Sync with webui setting",
"themeFeedback": "Theme Feedback",
"themeSetting": "Theme Settings"
"brand": {
"kitchen": "Kitchen",
"lobe": "LobeHub",
"custom": "Custom"
},
"custom": {
"initializing": "StableDiffusion / LobeTheme is initializing, please wait..."
},
"footer": {
"resources": "Resources",
"community": "Community",
"help": "Help",
"moreProducts": "More Products"
},
"header": {
"feedback": "Feedback",
"switchTheme": "Switch Light/Dark Theme",
"setting": "Setting"
},
"modal": {
"themeFeedback": {
"title": "Theme Feedback"
},
"themeSetting": {
"title": "Theme Settings"
}
},
"prompt": {
"load": "Load Prompt",
"set": "Set Prompt",
"negative": "Negative",
"positive": "Positive"
},
"setting": {
"button": {
"reset": "Reset",
"submit": "Apply and Restart Interface"
},
"confirmPageUnload": {
"title": "Confirmation on page leaving",
"desc": "Helps prevent loss of unsaved data"
},
"customFont": {
"title": "Custom Font",
"desc": "When enabled, it will automatically load a webfont to enhance the display of text in Chinese, English, and code"
},
"customLogo": {
"title": "Custom Logo",
"desc": "Support URL / Base64 / Emoji symbols"
},
"customTitle": {
"title": "Custom Title",
"desc": "Custom Logo Title"
},
"extraNetworkSidebar": {
"defaultCardSize": {
"title": "Model Cover Size",
"desc": "Default value of model cover size when starting"
},
"defaultExpand": {
"title": "Default Expand",
"desc": "Whether to expand the sidebar by default when starting"
},
"defaultWidth": {
"title": "Default Width",
"desc": "Default width of the sidebar when starting"
},
"displayMode": {
"title": "Display Mode",
"desc": "Fixed as grid mode for constant display, auto-expand when the mouse moves to the side in floating mode"
},
"enable": {
"title": "Enable",
"desc": "Enable the extra network sidebar on the right side"
}
},
"group": {
"extraNetworkSidebar": "Extra Network Sidebar",
"layout": "Layout Settings",
"promptTextarea": "Prompt Textbox",
"quickSettingSidebar": "Quick Setting Sidebar",
"theme": "Theme Settings"
},
"hideFooter": {
"title": "Hide Footer",
"desc": "Hide the theme footer and only display the default footer of stable diffusion webui"
},
"language": {
"title": "Language",
"desc": "Lobe Theme language"
},
"logoType": {
"title": "Logo Type",
"desc": "Logo Type",
"preview": "Preview"
},
"neutralColor": {
"title": "Neutral Color",
"desc": "Customize different shades of gray with different color tendencies, the second one is the original Kitchen neutral color"
},
"primaryColor": {
"title": "Primary Color",
"desc": "Custom primary color, the second one is the original Kitchen theme color"
},
"promptDisplayMode": {
"title": "Prompt Display Mode",
"desc": "Fixed height or auto height with draggable resize support",
"resizable": "Resizable",
"scroll": "Scroll"
},
"promptEditor": {
"title": "Prompt Editor",
"desc": "Provide a simple prompt editor at the top of the quick setting sidebar"
},
"promptHighlight": {
"title": "Prompt Syntax Highlighting",
"desc": "Automatically colorize prompt display according to the Stable Diffusion syntax rules"
},
"quickSettingSidebar": {
"defaultExpand": {
"title": "Default Expand",
"desc": "Whether to expand the sidebar by default when starting"
},
"defaultWidth": {
"title": "Default Width",
"desc": "Default width of the sidebar when starting"
},
"displayMode": {
"title": "Display Mode",
"desc": "Fixed as grid mode for constant display, auto-expand when the mouse moves to the side in floating mode"
},
"enable": {
"title": "Enable",
"desc": "Enable the quick setting sidebar on the left side"
}
},
"reduceAnimation": {
"title": "Reduce Animation",
"desc": "Reduce the blur effect and background flow color, which can improve smoothness and save CPU usage"
},
"splitPreviewer": {
"title": "Split Previewer",
"desc": "Put the prompt input box on the left and the generate button on the right, ensuring that the generated image is always displayed at the top when scrolling (experimental)"
},
"svgIcons": {
"title": "SVG Icons",
"desc": "Replace all Emoji icons in stable diffusion webui with SVG icons globally"
}
},
"sidebar": {
"extraNetwork": "Extra Network",
"quickSetting": "Quick Setting",
"mode": {
"fixed": "Fixed",
"float": "Float"
}
}
}

View File

@ -1,82 +1,159 @@
{
"appInitializing": "StableDiffusion/LobeTheme se está inicializando, espere...",
"community": "Comunidad",
"custom": "Personalizado",
"extraNetwork": "Red Adicional",
"feedback": "Comentarios",
"fixed": "Fijado",
"float": "Flotante",
"help": "Ayuda",
"kitchen": "Kitchen",
"loadPrompt": "Cargar Prompt",
"lobe": "Lobe",
"moreProducts": "Más Productos",
"negative": "Negativo",
"positive": "Positivo",
"quickSetting": "Configuración Rápida",
"resizable": "Redimensionable",
"resources": "Recursos",
"scroll": "Desplazarse",
"setPrompt": "Establecer Prompt",
"setting": "Configuración",
"settingButtonReset": "Reiniciar",
"settingButtonSubmit": "Aplicar y reiniciar la interfaz",
"settingConfirmPageUnload": "Confirma el cierre de la página",
"settingConfirmPageUnloadDesc": "Ayuda a evitar la pérdida de datos no guardados",
"settingCustomFont": "Cargar fuente personalizada",
"settingCustomFontDesc": "Cuando esté habilitado, cargará automáticamente una fuente web para mejorar la visualización del texto en chino, inglés y código",
"settingCustomLogo": "Logo personalizado",
"settingCustomLogoDesc": "URL de soporte/Base64/símbolos Emoji",
"settingCustomTitle": "Título personalizado",
"settingCustomTitleDesc": "Título del logo personalizado",
"settingExtraNetworkSidebarDefaultCardSize": "Tamaño de portada del modelo",
"settingExtraNetworkSidebarDefaultCardSizeDesc": "Valor predeterminado del tamaño de la cubierta del modelo al iniciar",
"settingExtraNetworkSidebarDefaultExpand": "Expansión predeterminada",
"settingExtraNetworkSidebarDefaultExpandDesc": "Si se expande la barra lateral de forma predeterminada al iniciar",
"settingExtraNetworkSidebarDefaultWidth": "Ancho predeterminado",
"settingExtraNetworkSidebarDefaultWidthDesc": "Ancho predeterminado de la barra lateral al iniciar",
"settingExtraNetworkSidebarDisplayMode": "Modo de visualización",
"settingExtraNetworkSidebarDisplayModeDesc": "Fijo a modo de cuadrícula para visualización constante, se expande automáticamente cuando el mouse se mueve hacia un lado en modo flotante",
"settingExtraNetworkSidebarEnable": "Habilitar",
"settingExtraNetworkSidebarEnableDesc": "Habilita la barra lateral de red adicional en el lado derecho",
"settingGroupExtraNetworkSidebar": "Barra lateral de red adicional",
"settingGroupLayout": "Ajustes de diseño",
"settingGroupPromptTextarea": "Cuadro de texto del Prompt",
"settingGroupQuickSettingSidebar": "Barra lateral de configuración rápida",
"settingGroupTheme": "Configuración del tema",
"settingHideFooter": "Ocultar pie de página",
"settingHideFooterDesc": "Ocultar el pie de página del tema y mostrar solo el pie de página predeterminado de la webui de stable diffusion webui",
"settingLanguage": "Idioma",
"settingLanguageDesc": "Idioma del tema Lobe",
"settingLogoPreview": "Vista previa",
"settingLogoType": "Tipo de Logo",
"settingLogoTypeDesc": "Tipo de logo",
"settingNeutralColor": "Color Neutro",
"settingNeutralColorDesc": "Personaliza diferentes tonos de gris con diferentes tendencias de color, el segundo es el color neutro original del tema Kitchen",
"settingPrimaryColor": "Color primario",
"settingPrimaryColorDesc": "Color primario personalizado, el segundo es el color original del tema Kitchen",
"settingPromptDisplayMode": "Modo de Visualización",
"settingPromptDisplayModeDesc": "Altura fija o altura automática con soporte para cambio de tamaño arrastrable",
"settingPromptEditor": "Editor de Prompts",
"settingPromptEditorDesc": "Proporciona un editor de Prompts sencillo en la parte superior de la barra lateral de configuración rápida",
"settingPromptHighlight": "Resaltar sintaxis del Prompt",
"settingPromptHighlightDesc": "Colorear automáticamente la visualización del prompt de acuerdo con las reglas de sintaxis de Stable Diffusion",
"settingQuickSettingSidebarDefaultExpand": "Expansión predeterminada",
"settingQuickSettingSidebarDefaultExpandDesc": "Si se expande la barra lateral de forma predeterminada al iniciar",
"settingQuickSettingSidebarDefaultWidth": "Ancho predeterminado",
"settingQuickSettingSidebarDefaultWidthDesc": "Ancho predeterminado de la barra lateral al iniciar",
"settingQuickSettingSidebarDisplayMode": "Modo de Visualización",
"settingQuickSettingSidebarDisplayModeDesc": "Fijo como modo de cuadrícula para visualización constante, se expande automáticamente cuando el mouse se mueve hacia un lado en el modo flotante",
"settingQuickSettingSidebarEnable": "Habilitar",
"settingQuickSettingSidebarEnableDesc": "Habilita la barra lateral de configuración rápida en el lado izquierdo",
"settingReduceAnimation": "Reducir animación",
"settingReduceAnimationDesc": "Reduce el efecto de desenfoque y el color del flujo de fondo, lo que puede mejorar la suavidad y ahorrar uso de CPU",
"settingSplitPreviewer": "Vista Previa Dividida",
"settingSplitPreviewerDesc": "Coloque el cuadro de entrada de solicitud a la izquierda y el botón generar a la derecha, asegurándose de que la imagen generada siempre se muestre en la parte superior al desplazarse (experimental)",
"settingSvgIcons": "Usar Iconos SVG",
"settingSvgIconsDesc": "Reemplazar globalmente todos los íconos Emoji en la interfaz de Stable Diffusion con íconos SVG",
"switchTheme": "Cambiar Tema Claro/Oscuro",
"sync": "Sincronizar con la configuración Webui",
"themeFeedback": "Comentarios del Tema",
"themeSetting": "Configuración del Tema"
"brand": {
"kitchen": "Kitchen",
"lobe": "LobeHub",
"custom": "Personalizado"
},
"custom": {
"initializing": "StableDiffusion / LobeTheme se está inicializando, por favor espere..."
},
"footer": {
"resources": "Recursos",
"community": "Comunidad",
"help": "Ayuda",
"moreProducts": "Más productos"
},
"header": {
"feedback": "Comentarios",
"switchTheme": "Cambiar tema claro/oscuro",
"setting": "Configuración"
},
"modal": {
"themeFeedback": {
"title": "Comentarios sobre el tema"
},
"themeSetting": {
"title": "Configuración del tema"
}
},
"prompt": {
"load": "Cargar aviso",
"set": "Establecer aviso",
"negative": "Negativo",
"positive": "Positivo"
},
"setting": {
"button": {
"reset": "Restablecer",
"submit": "Aplicar y reiniciar interfaz"
},
"confirmPageUnload": {
"title": "Confirmación al abandonar la página",
"desc": "Ayuda a prevenir la pérdida de datos no guardados"
},
"customFont": {
"title": "Fuente personalizada",
"desc": "Cuando está habilitada, cargará automáticamente una webfont para mejorar la visualización del texto en chino, inglés y código"
},
"customLogo": {
"title": "Logotipo personalizado",
"desc": "Soporta URL / Base64 / Símbolos Emoji"
},
"customTitle": {
"title": "Título personalizado",
"desc": "Título del logotipo personalizado"
},
"extraNetworkSidebar": {
"defaultCardSize": {
"title": "Tamaño de portada del modelo",
"desc": "Valor predeterminado del tamaño de portada del modelo al iniciar"
},
"defaultExpand": {
"title": "Expansión predeterminada",
"desc": "Si expandir la barra lateral de forma predeterminada al iniciar"
},
"defaultWidth": {
"title": "Ancho predeterminado",
"desc": "Ancho predeterminado de la barra lateral al iniciar"
},
"displayMode": {
"title": "Modo de visualización",
"desc": "Fijo como modo de cuadrícula para visualización constante, autoexpandible cuando el mouse se mueve hacia el lado en modo flotante"
},
"enable": {
"title": "Habilitar",
"desc": "Habilitar la barra lateral de red adicional en el lado derecho"
}
},
"group": {
"extraNetworkSidebar": "Barra lateral de red adicional",
"layout": "Configuración de diseño",
"promptTextarea": "Cuadro de texto de aviso",
"quickSettingSidebar": "Barra lateral de configuración rápida",
"theme": "Configuración del tema"
},
"hideFooter": {
"title": "Ocultar pie de página",
"desc": "Ocultar el pie de página del tema y solo mostrar el pie de página predeterminado de stable diffusion webui"
},
"language": {
"title": "Idioma",
"desc": "Idioma del tema Lóbulo"
},
"logoType": {
"title": "Tipo de logotipo",
"desc": "Tipo de logotipo",
"preview": "Vista previa"
},
"neutralColor": {
"title": "Color neutral",
"desc": "Personalizar diferentes tonos de gris con diferentes tendencias de color, el segundo es el color neutral original de Cocina"
},
"primaryColor": {
"title": "Color primario",
"desc": "Color primario personalizado, el segundo es el color del tema original de Cocina"
},
"promptDisplayMode": {
"title": "Modo de visualización de aviso",
"desc": "Altura fija o altura automática con soporte de redimensionamiento arrastrable",
"resizable": "Redimensionable",
"scroll": "Desplazamiento"
},
"promptEditor": {
"title": "Editor de aviso",
"desc": "Proporcionar un editor de aviso simple en la parte superior de la barra lateral de configuración rápida"
},
"promptHighlight": {
"title": "Resaltado de sintaxis de aviso",
"desc": "Colorear automáticamente la visualización del aviso según las reglas de sintaxis de Stable Diffusion"
},
"quickSettingSidebar": {
"defaultExpand": {
"title": "Expansión predeterminada",
"desc": "Si expandir la barra lateral de forma predeterminada al iniciar"
},
"defaultWidth": {
"title": "Ancho predeterminado",
"desc": "Ancho predeterminado de la barra lateral al iniciar"
},
"displayMode": {
"title": "Modo de visualización",
"desc": "Fijo como modo de cuadrícula para visualización constante, autoexpandible cuando el mouse se mueve hacia el lado en modo flotante"
},
"enable": {
"title": "Habilitar",
"desc": "Habilitar la barra lateral de configuración rápida en el lado izquierdo"
}
},
"reduceAnimation": {
"title": "Reducir animación",
"desc": "Reducir el efecto de desenfoque y el color de flujo de fondo, lo que puede mejorar la suavidad y ahorrar uso de CPU"
},
"splitPreviewer": {
"title": "Dividir previsualización",
"desc": "Colocar el cuadro de entrada de aviso a la izquierda y el botón de generación a la derecha, asegurando que la imagen generada se muestre siempre en la parte superior al desplazarse (experimental)"
},
"svgIcons": {
"title": "Iconos SVG",
"desc": "Reemplazar todos los iconos Emoji en stable diffusion webui con iconos SVG globalmente"
}
},
"sidebar": {
"extraNetwork": "Red adicional",
"quickSetting": "Configuración rápida",
"mode": {
"fixed": "Fijo",
"float": "Flotante"
}
}
}

159
locales/fr_FR.json Normal file
View File

@ -0,0 +1,159 @@
{
"brand": {
"kitchen": "Kitchen",
"lobe": "LobeHub",
"custom": "Personnalisé"
},
"custom": {
"initializing": "StableDiffusion / LobeTheme est en cours d'initialisation, veuillez patienter..."
},
"footer": {
"resources": "Ressources",
"community": "Communauté",
"help": "Aide",
"moreProducts": "Plus de Produits"
},
"header": {
"feedback": "Retour",
"switchTheme": "Changer de thème clair/sombre",
"setting": "Réglage"
},
"modal": {
"themeFeedback": {
"title": "Retour sur le thème"
},
"themeSetting": {
"title": "Paramètres du thème"
}
},
"prompt": {
"load": "Charger la demande",
"set": "Définir la demande",
"negative": "Négatif",
"positive": "Positif"
},
"setting": {
"button": {
"reset": "Réinitialiser",
"submit": "Appliquer et redémarrer l'interface"
},
"confirmPageUnload": {
"title": "Confirmation de quitter la page",
"desc": "Aide à éviter la perte de données non enregistrées"
},
"customFont": {
"title": "Police personnalisée",
"desc": "Lorsqu'activée, elle chargera automatiquement une web police pour améliorer l'affichage du texte en chinois, anglais et code"
},
"customLogo": {
"title": "Logo personnalisé",
"desc": "Support URL / Base64 / Symboles Emoji"
},
"customTitle": {
"title": "Titre personnalisé",
"desc": "Titre du logo personnalisé"
},
"extraNetworkSidebar": {
"defaultCardSize": {
"title": "Taille de la couverture du modèle",
"desc": "Valeur par défaut de la taille de la couverture du modèle au démarrage"
},
"defaultExpand": {
"title": "Développement par défaut",
"desc": "S'il faut développer la barre latérale par défaut au démarrage"
},
"defaultWidth": {
"title": "Largeur par défaut",
"desc": "Largeur par défaut de la barre latérale au démarrage"
},
"displayMode": {
"title": "Mode d'affichage",
"desc": "Fixé en mode grille pour un affichage constant, auto-développement lorsque la souris se déplace sur le côté en mode flottant"
},
"enable": {
"title": "Activer",
"desc": "Activer la barre latérale réseau supplémentaire sur le côté droit"
}
},
"group": {
"extraNetworkSidebar": "Barre latérale réseau supplémentaire",
"layout": "Réglages de mise en page",
"promptTextarea": "Zone de texte de la demande",
"quickSettingSidebar": "Barre latérale de réglage rapide",
"theme": "Paramètres du thème"
},
"hideFooter": {
"title": "Masquer le pied de page",
"desc": "Masquer le pied de page du thème et afficher uniquement le pied de page par défaut de stable diffusion webui"
},
"language": {
"title": "Langue",
"desc": "Langue du thème Lobe"
},
"logoType": {
"title": "Type de logo",
"desc": "Type de logo",
"preview": "Aperçu"
},
"neutralColor": {
"title": "Couleur neutre",
"desc": "Personnaliser différentes nuances de gris avec différentes tendances de couleur, la deuxième est la couleur neutre d'origine de Kitchen"
},
"primaryColor": {
"title": "Couleur primaire",
"desc": "Couleur primaire personnalisée, la deuxième est la couleur de thème d'origine de Kitchen"
},
"promptDisplayMode": {
"title": "Mode d'affichage de la demande",
"desc": "Hauteur fixe ou hauteur automatique avec prise en charge redimensionnable",
"resizable": "Redimensionnable",
"scroll": "Faire défiler"
},
"promptEditor": {
"title": "Éditeur de demande",
"desc": "Fournir un éditeur de demande simple en haut de la barre latérale de réglage rapide"
},
"promptHighlight": {
"title": "Mise en évidence de la syntaxe de la demande",
"desc": "Coloriser automatiquement l'affichage de la demande selon les règles de syntaxe de Stable Diffusion"
},
"quickSettingSidebar": {
"defaultExpand": {
"title": "Développement par défaut",
"desc": "S'il faut développer la barre latérale par défaut au démarrage"
},
"defaultWidth": {
"title": "Largeur par défaut",
"desc": "Largeur par défaut de la barre latérale au démarrage"
},
"displayMode": {
"title": "Mode d'affichage",
"desc": "Fixé en mode grille pour un affichage constant, auto-développement lorsque la souris se déplace sur le côté en mode flottant"
},
"enable": {
"title": "Activer",
"desc": "Activer la barre latérale de réglage rapide sur le côté gauche"
}
},
"reduceAnimation": {
"title": "Réduire l'animation",
"desc": "Réduire l'effet de flou et la couleur de fond en mouvement, ce qui peut améliorer la fluidité et économiser l'utilisation du processeur"
},
"splitPreviewer": {
"title": "Diviser le visualiseur",
"desc": "Placer la zone de saisie de la demande à gauche et le bouton de génération à droite, en veillant à ce que l'image générée soit toujours affichée en haut lors du défilement (expérimental)"
},
"svgIcons": {
"title": "Icônes SVG",
"desc": "Remplacer tous les icônes Emoji dans stable diffusion webui par des icônes SVG globalement"
}
},
"sidebar": {
"extraNetwork": "Réseau supplémentaire",
"quickSetting": "Réglage rapide",
"mode": {
"fixed": "Fixe",
"float": "Flottant"
}
}
}

View File

@ -1,82 +1,159 @@
{
"appInitializing": "StableDiffusion / LobeThemeが初期化中です。お待ちください...",
"community": "コミュニティ",
"custom": "カスタム",
"extraNetwork": "追加ネットワーク",
"feedback": "フィードバック",
"fixed": "固定",
"float": "フロート",
"help": "ヘルプ",
"kitchen": "キッチン",
"loadPrompt": "プロンプトをロード",
"lobe": "ローブ",
"moreProducts": "その他の製品",
"negative": "ネガティブなヒント",
"positive": "ポジティブなヒント",
"quickSetting": "クイック設定",
"resizable": "リサイズ可能",
"resources": "関連リソース",
"scroll": "スクロール",
"setPrompt": "プロンプトを設定",
"setting": "設定",
"settingButtonReset": "リセット",
"settingButtonSubmit": "適用して再起動",
"settingConfirmPageUnload": "ページのクローズ確認",
"settingConfirmPageUnloadDesc": "未保存のデータの損失を防ぐ",
"settingCustomFont": "カスタムフォントの読み込み",
"settingCustomFontDesc": "有効にすると、Webフォントを自動的に読み込んで、英語、中国語、およびコードの表示効果を最適化します",
"settingCustomLogo": "カスタムロゴ",
"settingCustomLogoDesc": "URL / Base64 / 絵文字をサポート",
"settingCustomTitle": "カスタムタイトル",
"settingCustomTitleDesc": "カスタムロゴのタイトル名",
"settingExtraNetworkSidebarDefaultCardSize": "モデルカバーサイズ",
"settingExtraNetworkSidebarDefaultCardSizeDesc": "起動時のモデルカバーサイズのデフォルト値",
"settingExtraNetworkSidebarDefaultExpand": "デフォルトで展開",
"settingExtraNetworkSidebarDefaultExpandDesc": "起動時にサイドバーをデフォルトで展開しますか?",
"settingExtraNetworkSidebarDefaultWidth": "デフォルト幅",
"settingExtraNetworkSidebarDefaultWidthDesc": "起動時のサイドバーのデフォルト幅",
"settingExtraNetworkSidebarDisplayMode": "表示モード",
"settingExtraNetworkSidebarDisplayModeDesc": "グリッドモードで常に表示するか、ホバー時に自動的に展開するフロートモードで表示するか",
"settingExtraNetworkSidebarEnable": "有効にする",
"settingExtraNetworkSidebarEnableDesc": "右側の追加ネットワークサイドバーを有効にする",
"settingGroupExtraNetworkSidebar": "追加ネットワークサイドバー",
"settingGroupLayout": "レイアウト設定",
"settingGroupPromptTextarea": "プロンプトテキストエリア",
"settingGroupQuickSettingSidebar": "クイック設定サイドバー",
"settingGroupTheme": "テーマ設定",
"settingHideFooter": "フッターを非表示にする",
"settingHideFooterDesc": "テーマのフッターを非表示にし、stable diffusion webui のデフォルトフッターのみ表示します",
"settingLanguage": "言語",
"settingLanguageDesc": "Lobe Themeの言語",
"settingLogoPreview": "プレビュー",
"settingLogoType": "ロゴタイプ",
"settingLogoTypeDesc": "ロゴタイプ",
"settingNeutralColor": "中立色",
"settingNeutralColorDesc": "異なる色相のグレースケールのカスタマイズ。2番目は元のKitchenの中立色です",
"settingPrimaryColor": "プライマリカラー",
"settingPrimaryColorDesc": "カスタムプライマリカラー。2番目は元のKitchenのプライマリカラーです",
"settingPromptDisplayMode": "表示モード",
"settingPromptDisplayModeDesc": "固定の高さまたはドラッグリサイズをサポートする自動の高さ",
"settingPromptEditor": "プロンプトエディタ",
"settingPromptEditorDesc": "クイック設定サイドバーの上部に簡単なプロンプトエディタを提供します",
"settingPromptHighlight": "Promptのシンタックスハイライト",
"settingPromptHighlightDesc": "Stable Diffusionのシンタックスルールに基づいて、promptの表示を自動的にハイライトします",
"settingQuickSettingSidebarDefaultExpand": "デフォルトで展開",
"settingQuickSettingSidebarDefaultExpandDesc": "起動時にサイドバーをデフォルトで展開しますか?",
"settingQuickSettingSidebarDefaultWidth": "デフォルト幅",
"settingQuickSettingSidebarDefaultWidthDesc": "起動時のサイドバーのデフォルト幅",
"settingQuickSettingSidebarDisplayMode": "表示モード",
"settingQuickSettingSidebarDisplayModeDesc": "グリッドモードで常に表示するか、ホバー時に自動的に展開するフロートモードで表示するか",
"settingQuickSettingSidebarEnable": "有効にする",
"settingQuickSettingSidebarEnableDesc": "左側のクイック設定サイドバーを有効にする",
"settingReduceAnimation": "アニメーションを削減",
"settingReduceAnimationDesc": "ガラスのエフェクトと背景の流れる色を削減し、スムーズさを向上させ、CPUの使用量を節約できます",
"settingSplitPreviewer": "2列モード",
"settingSplitPreviewerDesc": "プロンプト入力ボックスを左側に配置し、生成ボタンを右側に配置し、スクロール時に生成された画像が常にトップに表示されるようにします(実験的)",
"settingSvgIcons": "SVGアイコンを使用",
"settingSvgIconsDesc": "stable diffusion webuiの絵文字アイコンをすべてSVGアイコンに置き換えます",
"switchTheme": "明暗テーマを切り替える",
"sync": "WebUIの設定と同期する",
"themeFeedback": "テーマのフィードバック",
"themeSetting": "テーマ設定"
"brand": {
"kitchen": "Kitchen",
"lobe": "LobeHub",
"custom": "カスタム"
},
"custom": {
"initializing": "StableDiffusion / LobeTheme が初期化中です。お待ちください..."
},
"footer": {
"resources": "リソース",
"community": "コミュニティ",
"help": "ヘルプ",
"moreProducts": "その他の製品"
},
"header": {
"feedback": "フィードバック",
"switchTheme": "ライト/ダークテーマの切り替え",
"setting": "設定"
},
"modal": {
"themeFeedback": {
"title": "テーマフィードバック"
},
"themeSetting": {
"title": "テーマ設定"
}
},
"prompt": {
"load": "プロンプトの読み込み",
"set": "プロンプトの設定",
"negative": "ネガティブ",
"positive": "ポジティブ"
},
"setting": {
"button": {
"reset": "リセット",
"submit": "適用してインターフェースを再起動"
},
"confirmPageUnload": {
"title": "ページ離脱の確認",
"desc": "未保存のデータの損失を防ぐのに役立ちます"
},
"customFont": {
"title": "カスタムフォント",
"desc": "有効にすると、中国語、英語、コードのテキストの表示を向上させるために自動的にWebフォントを読み込みます"
},
"customLogo": {
"title": "カスタムロゴ",
"desc": "URL / Base64 / 絵文字シンボルをサポート"
},
"customTitle": {
"title": "カスタムタイトル",
"desc": "カスタムロゴのタイトル"
},
"extraNetworkSidebar": {
"defaultCardSize": {
"title": "モデルカバーサイズ",
"desc": "開始時のモデルカバーサイズのデフォルト値"
},
"defaultExpand": {
"title": "デフォルト展開",
"desc": "開始時にサイドバーをデフォルトで展開するかどうか"
},
"defaultWidth": {
"title": "デフォルト幅",
"desc": "開始時のサイドバーのデフォルト幅"
},
"displayMode": {
"title": "表示モード",
"desc": "固定表示のグリッドモードまたは浮動モードでサイドにマウスを移動すると自動的に展開"
},
"enable": {
"title": "有効にする",
"desc": "右側に追加のネットワークサイドバーを有効にする"
}
},
"group": {
"extraNetworkSidebar": "追加のネットワークサイドバー",
"layout": "レイアウト設定",
"promptTextarea": "プロンプトテキストボックス",
"quickSettingSidebar": "クイック設定サイドバー",
"theme": "テーマ設定"
},
"hideFooter": {
"title": "フッターを非表示",
"desc": "テーマのフッターを非表示にし、Stable Diffusion WebUI のデフォルトフッターのみを表示"
},
"language": {
"title": "言語",
"desc": "Lobeテーマの言語"
},
"logoType": {
"title": "ロゴタイプ",
"desc": "ロゴタイプ",
"preview": "プレビュー"
},
"neutralColor": {
"title": "ニュートラルカラー",
"desc": "異なる色傾向のグレーの異なるシェードをカスタマイズします。2番目は元のキッチンのニュートラルカラーです"
},
"primaryColor": {
"title": "プライマリカラー",
"desc": "カスタムプライマリカラー。2番目は元のキッチンのテーマカラーです"
},
"promptDisplayMode": {
"title": "プロンプト表示モード",
"desc": "固定高さまたはドラッグ可能なリサイズサポート付きの自動高さ",
"resizable": "リサイズ可能",
"scroll": "スクロール"
},
"promptEditor": {
"title": "プロンプトエディタ",
"desc": "クイック設定サイドバーの上部にシンプルなプロンプトエディタを提供"
},
"promptHighlight": {
"title": "プロンプトシンタックスハイライト",
"desc": "Stable Diffusionの構文ルールに従ってプロンプト表示を自動的にカラーリング"
},
"quickSettingSidebar": {
"defaultExpand": {
"title": "デフォルト展開",
"desc": "開始時にサイドバーをデフォルトで展開するかどうか"
},
"defaultWidth": {
"title": "デフォルト幅",
"desc": "開始時のサイドバーのデフォルト幅"
},
"displayMode": {
"title": "表示モード",
"desc": "固定表示のグリッドモードまたは浮動モードでサイドにマウスを移動すると自動的に展開"
},
"enable": {
"title": "有効にする",
"desc": "左側にクイック設定サイドバーを有効にする"
}
},
"reduceAnimation": {
"title": "アニメーションを削減",
"desc": "ぼかし効果と背景フローカラーを削減し、滑らかさを向上させCPU使用量を節約"
},
"splitPreviewer": {
"title": "分割プレビューア",
"desc": "プロンプト入力ボックスを左側に配置し、生成ボタンを右側に配置し、スクロール時に常に生成された画像が上部に表示されるようにします(実験的)"
},
"svgIcons": {
"title": "SVGアイコン",
"desc": "Stable Diffusion WebUI のすべての絵文字アイコンをグローバルにSVGアイコンで置き換えます"
}
},
"sidebar": {
"extraNetwork": "追加のネットワーク",
"quickSetting": "クイック設定",
"mode": {
"fixed": "固定",
"float": "フロート"
}
}
}

View File

@ -1,82 +1,159 @@
{
"appInitializing": "StableDiffusion / LobeTheme가 초기화 중입니다. 잠시 기다려주세요...",
"community": "커뮤니티",
"custom": "사용자 정의",
"extraNetwork": "추가 네트워크",
"feedback": "피드백",
"fixed": "고정",
"float": "부유",
"help": "도움말",
"kitchen": "Kitchen",
"loadPrompt": "로드 프롬프트",
"lobe": "Lobe",
"moreProducts": "더 많은 제품",
"negative": "부정적인",
"positive": "긍정적인",
"quickSetting": "빠른 설정",
"resizable": "크기 조절 가능",
"resources": "관련 자료",
"scroll": "스크롤",
"setPrompt": "프롬프트 설정",
"setting": "설정",
"settingButtonReset": "재설정",
"settingButtonSubmit": "적용 및 인터페이스 재시작",
"settingConfirmPageUnload": "페이지 닫기 확인",
"settingConfirmPageUnloadDesc": "저장되지 않은 데이터 손실 방지",
"settingCustomFont": "폰트 로드",
"settingCustomFontDesc": "이 기능을 사용하면 웹 폰트를 자동으로 로드하여 영문, 한글 및 코드 표시 효과를 최적화합니다",
"settingCustomLogo": "사용자 정의 로고",
"settingCustomLogoDesc": "URL / Base64 / 이모지 표정을 지원합니다.",
"settingCustomTitle": "사용자 정의 제목",
"settingCustomTitleDesc": "사용자 정의 로고 제목",
"settingExtraNetworkSidebarDefaultCardSize": "모델 커버 크기",
"settingExtraNetworkSidebarDefaultCardSizeDesc": "시작시 모델 커버 크기 기본값",
"settingExtraNetworkSidebarDefaultExpand": "기본 확장",
"settingExtraNetworkSidebarDefaultExpandDesc": "시작시 사이드바 기본 확장 여부",
"settingExtraNetworkSidebarDefaultWidth": "기본 너비",
"settingExtraNetworkSidebarDefaultWidthDesc": "시작시 사이드바 기본 너비",
"settingExtraNetworkSidebarDisplayMode": "표시 모드",
"settingExtraNetworkSidebarDisplayModeDesc": "그리드 모드로 고정하여 항상 표시하거나, 부유 모드로 설정하여 사이드바에 마우스를 가져가면 자동으로 확장",
"settingExtraNetworkSidebarEnable": "사용",
"settingExtraNetworkSidebarEnableDesc": "오른쪽에 추가 네트워크 사이드바 활성화",
"settingGroupExtraNetworkSidebar": "추가 네트워크 사이드바",
"settingGroupLayout": "레이아웃 설정",
"settingGroupPromptTextarea": "프롬프트 텍스트 영역",
"settingGroupQuickSettingSidebar": "빠른 설정 사이드바",
"settingGroupTheme": "테마 설정",
"settingHideFooter": "푸터 숨기기",
"settingHideFooterDesc": "테마 푸터를 숨기고 stable diffusion webui의 기본 푸터만 표시",
"settingLanguage": "언어",
"settingLanguageDesc": "Lobe Theme 테마 언어",
"settingLogoPreview": "미리보기",
"settingLogoType": "로고 유형",
"settingLogoTypeDesc": "로고 유형",
"settingNeutralColor": "중립색",
"settingNeutralColorDesc": "다른 색상 경향의 그레이 스케일 사용자 정의, 두 번째는 원래 Kitchen의 중립색",
"settingPrimaryColor": "기본 색상",
"settingPrimaryColorDesc": "사용자 정의 기본 색상, 두 번째는 원래 Kitchen의 기본 색상",
"settingPromptDisplayMode": "표시 모드",
"settingPromptDisplayModeDesc": "고정 높이 또는 자동 높이 및 드래그 조절 지원",
"settingPromptEditor": "프롬프트 편집기",
"settingPromptEditorDesc": "빠른 설정 사이드바 상단에 간단한 프롬프트 편집기 제공",
"settingPromptHighlight": "Prompt 구문 강조",
"settingPromptHighlightDesc": "Stable Diffusion 구문 규칙에 따라 자동으로 prompt를 강조하여 표시합니다",
"settingQuickSettingSidebarDefaultExpand": "기본 확장",
"settingQuickSettingSidebarDefaultExpandDesc": "시작시 사이드바 기본 확장 여부",
"settingQuickSettingSidebarDefaultWidth": "기본 너비",
"settingQuickSettingSidebarDefaultWidthDesc": "시작시 사이드바 기본 너비",
"settingQuickSettingSidebarDisplayMode": "표시 모드",
"settingQuickSettingSidebarDisplayModeDesc": "그리드 모드로 고정하여 항상 표시하거나, 부유 모드로 설정하여 사이드바에 마우스를 가져가면 자동으로 확장",
"settingQuickSettingSidebarEnable": "사용",
"settingQuickSettingSidebarEnableDesc": "왼쪽에 빠른 설정 사이드바 활성화",
"settingReduceAnimation": "애니메이션 줄이기",
"settingReduceAnimationDesc": "유리 효과와 배경 흐름 색상을 줄여서 부드러움을 향상시키고 CPU 사용량을 줄일 수 있습니다.",
"settingSplitPreviewer": "이중 열 모드",
"settingSplitPreviewerDesc": "프롬프트 입력 상자를 왼쪽에 배치하고, 우측에 생성 버튼을 두어 스크롤 시 생성된 이미지가 항상 위에 표시되도록 합니다 (실험적)",
"settingSvgIcons": "SVG 아이콘 사용",
"settingSvgIconsDesc": "stable diffusion webui의 이모지 아이콘을 전역적으로 SVG 아이콘으로 교체합니다.",
"switchTheme": "밝기 테마 전환",
"sync": "웹 UI 설정과 동기화",
"themeFeedback": "테마 피드백",
"themeSetting": "테마 설정"
"brand": {
"kitchen": "Kitchen",
"lobe": "LobeHub",
"custom": "사용자 정의"
},
"custom": {
"initializing": "StableDiffusion / LobeTheme이 초기화 중입니다. 잠시 기다려주세요..."
},
"footer": {
"resources": "자료",
"community": "커뮤니티",
"help": "도움말",
"moreProducts": "더 많은 제품"
},
"header": {
"feedback": "피드백",
"switchTheme": "밝은/어두운 테마 전환",
"setting": "설정"
},
"modal": {
"themeFeedback": {
"title": "테마 피드백"
},
"themeSetting": {
"title": "테마 설정"
}
},
"prompt": {
"load": "로드 프롬프트",
"set": "설정 프롬프트",
"negative": "부정적",
"positive": "긍정적"
},
"setting": {
"button": {
"reset": "재설정",
"submit": "적용 및 인터페이스 재시작"
},
"confirmPageUnload": {
"title": "페이지 이탈 확인",
"desc": "저장되지 않은 데이터 손실을 방지하는 데 도움이 됩니다."
},
"customFont": {
"title": "사용자 정의 글꼴",
"desc": "활성화되면 중국어, 영어 및 코드의 텍스트 표시를 향상시키기 위해 자동으로 웹 글꼴을 로드합니다."
},
"customLogo": {
"title": "사용자 정의 로고",
"desc": "URL / Base64 / 이모지 심볼 지원"
},
"customTitle": {
"title": "사용자 정의 제목",
"desc": "사용자 정의 로고 제목"
},
"extraNetworkSidebar": {
"defaultCardSize": {
"title": "모델 커버 크기",
"desc": "시작할 때 모델 커버 크기의 기본값"
},
"defaultExpand": {
"title": "기본 확장",
"desc": "시작할 때 사이드바를 기본적으로 확장할지 여부"
},
"defaultWidth": {
"title": "기본 너비",
"desc": "시작할 때 사이드바의 기본 너비"
},
"displayMode": {
"title": "표시 모드",
"desc": "고정된 그리드 모드로 상시 표시하거나 부유 모드에서 측면으로 마우스를 이동할 때 자동으로 확장"
},
"enable": {
"title": "활성화",
"desc": "오른쪽에 추가 네트워크 사이드바 활성화"
}
},
"group": {
"extraNetworkSidebar": "추가 네트워크 사이드바",
"layout": "레이아웃 설정",
"promptTextarea": "프롬프트 텍스트 상자",
"quickSettingSidebar": "빠른 설정 사이드바",
"theme": "테마 설정"
},
"hideFooter": {
"title": "푸터 숨기기",
"desc": "테마 푸터를 숨기고 Stable Diffusion 웹UI의 기본 푸터만 표시"
},
"language": {
"title": "언어",
"desc": "로브 테마 언어"
},
"logoType": {
"title": "로고 유형",
"desc": "로고 유형",
"preview": "미리보기"
},
"neutralColor": {
"title": "중립 색상",
"desc": "다른 색상 경향을 가진 다양한 회색 음영을 사용자 정의합니다. 두 번째는 원래 주방의 중립 색상입니다."
},
"primaryColor": {
"title": "기본 색상",
"desc": "사용자 정의 기본 색상, 두 번째는 원래 주방 테마 색상입니다."
},
"promptDisplayMode": {
"title": "프롬프트 표시 모드",
"desc": "고정 높이 또는 드래그 가능한 크기 조정 지원을 통한 자동 높이",
"resizable": "크기 조절 가능",
"scroll": "스크롤"
},
"promptEditor": {
"title": "프롬프트 편집기",
"desc": "빠른 설정 사이드바 상단에 간단한 프롬프트 편집기 제공"
},
"promptHighlight": {
"title": "프롬프트 구문 강조",
"desc": "Stable Diffusion 구문 규칙에 따라 프롬프트 표시를 자동으로 색상화"
},
"quickSettingSidebar": {
"defaultExpand": {
"title": "기본 확장",
"desc": "시작할 때 사이드바를 기본적으로 확장할지 여부"
},
"defaultWidth": {
"title": "기본 너비",
"desc": "시작할 때 사이드바의 기본 너비"
},
"displayMode": {
"title": "표시 모드",
"desc": "고정된 그리드 모드로 상시 표시하거나 부유 모드에서 측면으로 마우스를 이동할 때 자동으로 확장"
},
"enable": {
"title": "활성화",
"desc": "왼쪽에 빠른 설정 사이드바 활성화"
}
},
"reduceAnimation": {
"title": "애니메이션 줄이기",
"desc": "부드러움을 향상시키고 CPU 사용량을 줄일 수 있는 흐림 효과와 배경 흐름 색상을 줄입니다."
},
"splitPreviewer": {
"title": "분할 미리보기",
"desc": "프롬프트 입력 상자를 왼쪽에 놓고 생성 버튼을 오른쪽에 놓아 스크롤할 때 항상 생성된 이미지가 위에 표시되도록 합니다 (실험적)"
},
"svgIcons": {
"title": "SVG 아이콘",
"desc": "Stable Diffusion 웹UI의 모든 이모지 아이콘을 전역적으로 SVG 아이콘으로 대체"
}
},
"sidebar": {
"extraNetwork": "추가 네트워크",
"quickSetting": "빠른 설정",
"mode": {
"fixed": "고정",
"float": "부유"
}
}
}

View File

@ -15,16 +15,28 @@
"label": "日本語",
"value": "ja_JP"
},
{
"label": "Русский",
"value": "ru_RU"
},
{
"label": "한국어",
"value": "ko_KR"
},
{
"label": "Français",
"value": "fr_FR"
},
{
"label": "Deutsch",
"value": "de_DE"
},
{
"label": "Русский",
"value": "ru_RU"
},
{
"label": "Español",
"value": "es_ES"
},
{
"label": "Português",
"value": "pt_BR"
}
]

159
locales/pt_BR.json Normal file
View File

@ -0,0 +1,159 @@
{
"brand": {
"kitchen": "Kitchen",
"lobe": "LobeHub",
"custom": "Personalizado"
},
"custom": {
"initializing": "StableDiffusion / LobeTheme está inicializando, por favor aguarde..."
},
"footer": {
"resources": "Recursos",
"community": "Comunidade",
"help": "Ajuda",
"moreProducts": "Mais Produtos"
},
"header": {
"feedback": "Feedback",
"switchTheme": "Alternar Tema Claro/Escuro",
"setting": "Configuração"
},
"modal": {
"themeFeedback": {
"title": "Feedback do Tema"
},
"themeSetting": {
"title": "Configurações do Tema"
}
},
"prompt": {
"load": "Carregar Prompt",
"set": "Definir Prompt",
"negative": "Negativo",
"positive": "Positivo"
},
"setting": {
"button": {
"reset": "Redefinir",
"submit": "Aplicar e Reiniciar Interface"
},
"confirmPageUnload": {
"title": "Confirmação ao sair da página",
"desc": "Ajuda a evitar a perda de dados não salvos"
},
"customFont": {
"title": "Fonte Personalizada",
"desc": "Quando ativado, carregará automaticamente uma webfont para aprimorar a exibição de texto em chinês, inglês e código"
},
"customLogo": {
"title": "Logotipo Personalizado",
"desc": "Suporte URL / Base64 / Símbolos de Emoji"
},
"customTitle": {
"title": "Título Personalizado",
"desc": "Título do Logotipo Personalizado"
},
"extraNetworkSidebar": {
"defaultCardSize": {
"title": "Tamanho da Capa do Modelo",
"desc": "Valor padrão do tamanho da capa do modelo ao iniciar"
},
"defaultExpand": {
"title": "Expansão Padrão",
"desc": "Se deve expandir a barra lateral por padrão ao iniciar"
},
"defaultWidth": {
"title": "Largura Padrão",
"desc": "Largura padrão da barra lateral ao iniciar"
},
"displayMode": {
"title": "Modo de Exibição",
"desc": "Fixo como modo de grade para exibição constante, auto-expandir quando o mouse se move para o lado no modo flutuante"
},
"enable": {
"title": "Habilitar",
"desc": "Habilitar a barra lateral de rede extra no lado direito"
}
},
"group": {
"extraNetworkSidebar": "Barra Lateral de Rede Extra",
"layout": "Configurações de Layout",
"promptTextarea": "Caixa de Texto do Prompt",
"quickSettingSidebar": "Barra Lateral de Configuração Rápida",
"theme": "Configurações do Tema"
},
"hideFooter": {
"title": "Ocultar Rodapé",
"desc": "Ocultar o rodapé do tema e exibir apenas o rodapé padrão do stable diffusion webui"
},
"language": {
"title": "Idioma",
"desc": "Idioma do Tema Lobe"
},
"logoType": {
"title": "Tipo de Logotipo",
"desc": "Tipo de Logotipo",
"preview": "Visualização"
},
"neutralColor": {
"title": "Cor Neutra",
"desc": "Personalize diferentes tons de cinza com diferentes tendências de cor, o segundo é a cor neutra original da Cozinha"
},
"primaryColor": {
"title": "Cor Primária",
"desc": "Cor primária personalizada, o segundo é a cor do tema original da Cozinha"
},
"promptDisplayMode": {
"title": "Modo de Exibição do Prompt",
"desc": "Altura fixa ou altura automática com suporte para redimensionamento arrastável",
"resizable": "Redimensionável",
"scroll": "Rolagem"
},
"promptEditor": {
"title": "Editor de Prompt",
"desc": "Fornece um editor de prompt simples no topo da barra lateral de configuração rápida"
},
"promptHighlight": {
"title": "Realce de Sintaxe do Prompt",
"desc": "Colorize automaticamente a exibição do prompt de acordo com as regras de sintaxe do Stable Diffusion"
},
"quickSettingSidebar": {
"defaultExpand": {
"title": "Expansão Padrão",
"desc": "Se deve expandir a barra lateral por padrão ao iniciar"
},
"defaultWidth": {
"title": "Largura Padrão",
"desc": "Largura padrão da barra lateral ao iniciar"
},
"displayMode": {
"title": "Modo de Exibição",
"desc": "Fixo como modo de grade para exibição constante, auto-expandir quando o mouse se move para o lado no modo flutuante"
},
"enable": {
"title": "Habilitar",
"desc": "Habilitar a barra lateral de configuração rápida no lado esquerdo"
}
},
"reduceAnimation": {
"title": "Reduzir Animação",
"desc": "Reduzir o efeito de desfoque e cor de fundo, o que pode melhorar a suavidade e economizar o uso da CPU"
},
"splitPreviewer": {
"title": "Dividir Visualizador",
"desc": "Coloque a caixa de entrada do prompt à esquerda e o botão de geração à direita, garantindo que a imagem gerada seja sempre exibida no topo ao rolar (experimental)"
},
"svgIcons": {
"title": "Ícones SVG",
"desc": "Substituir todos os ícones de Emoji no stable diffusion webui por ícones SVG globalmente"
}
},
"sidebar": {
"extraNetwork": "Rede Extra",
"quickSetting": "Configuração Rápida",
"mode": {
"fixed": "Fixo",
"float": "Flutuante"
}
}
}

View File

@ -1,82 +1,159 @@
{
"appInitializing": "StableDiffusion / LobeTheme инициализируется, пожалуйста, подождите...",
"community": "Сообщество",
"custom": "Кастомный",
"extraNetwork": "Доп. Сети",
"feedback": "Отзыв",
"fixed": "Фиксированный",
"float": "Плавающий",
"help": "Помощь",
"kitchen": "Kitchen",
"loadPrompt": "Подгрузить промт",
"lobe": "Lobe",
"moreProducts": "Другие проекты",
"negative": "Отрицательные",
"positive": "Положительные",
"quickSetting": "Быстрые настройки",
"resizable": "Растягиваемый",
"resources": "Ресурсы",
"scroll": "Скрол",
"setPrompt": "Вставить промт",
"setting": "Настройки",
"settingButtonReset": "Сбросить",
"settingButtonSubmit": "Применить и перезапустить интерфейс",
"settingConfirmPageUnload": "Подтверждать закрытие страницы",
"settingConfirmPageUnloadDesc": "Помогает предотвратить потерю несохраненных данных",
"settingCustomFont": "Загружать спец. шрифт",
"settingCustomFontDesc": "Когда он включен, он автоматически загружает веб-шрифт для улучшения отображения текста на китайском, английском и кодовом языках.",
"settingCustomLogo": "Пользовательский логотип",
"settingCustomLogoDesc": "Поддержка символов URL/Base64/Emoji",
"settingCustomTitle": "Пользовательское название",
"settingCustomTitleDesc": "Название пользовательского логотипа",
"settingExtraNetworkSidebarDefaultCardSize": "Размер превью модели",
"settingExtraNetworkSidebarDefaultCardSizeDesc": "Значение по умолчанию размера обложки модели при запуске",
"settingExtraNetworkSidebarDefaultExpand": "Состояние разворота по умолчанию",
"settingExtraNetworkSidebarDefaultExpandDesc": "Нужно ли по умолчанию разворачивать боковую панель при запуске?",
"settingExtraNetworkSidebarDefaultWidth": "Ширина по умолчанию",
"settingExtraNetworkSidebarDefaultWidthDesc": "Ширина боковой панели по умолчанию при запуске",
"settingExtraNetworkSidebarDisplayMode": "Режим отображения",
"settingExtraNetworkSidebarDisplayModeDesc": "Фиксирован постоянно или появляется при наведении на боковую панель",
"settingExtraNetworkSidebarEnable": "Включено",
"settingExtraNetworkSidebarEnableDesc": "Включить Панель Доп. моделей справа",
"settingGroupExtraNetworkSidebar": "Боковая панель с доп сетями",
"settingGroupLayout": "Настройки макета",
"settingGroupPromptTextarea": "Промт(подсказка)",
"settingGroupQuickSettingSidebar": "Боковая панель быстрых настроек",
"settingGroupTheme": "Настройка темы",
"settingHideFooter": "Скрыть футтер",
"settingHideFooterDesc": "Скрыть футтер темы и отображать только футтер дефолтного веб-интерфейса.",
"settingLanguage": "Язык",
"settingLanguageDesc": "Язык Lobe темы",
"settingLogoPreview": "Предпросмотр",
"settingLogoType": "Стиль лого",
"settingLogoTypeDesc": "Стиль лого",
"settingNeutralColor": "Нейтральный цвет",
"settingNeutralColorDesc": "Настройте различные оттенки серого с различными цветовыми тенденциями, второй - оригинальный нейтральный цвет темы Kitchen.",
"settingPrimaryColor": "Основной цвет",
"settingPrimaryColorDesc": "Основной цвет - пользовательский, второй - оригинальный цвет темы Kitchen",
"settingPromptDisplayMode": "Режим отображения",
"settingPromptDisplayModeDesc": "Фиксированная высота или автоматическая высота с поддержкой перетаскиваемого изменения размера",
"settingPromptEditor": "Редактор промта",
"settingPromptEditorDesc": "Включить простой редактор подсказок в верхней части боковой панели быстрых настроек",
"settingPromptHighlight": "Подсветка синтаксиса промта",
"settingPromptHighlightDesc": "Автоматическое изменение цвета отображения подсказок в соответствии с правилами синтаксиса Stable Diffusion",
"settingQuickSettingSidebarDefaultExpand": "Состояние разворота по умолчанию",
"settingQuickSettingSidebarDefaultExpandDesc": "Нужно ли по умолчанию разворачивать боковую панель при запуске?",
"settingQuickSettingSidebarDefaultWidth": "Ширина по умолчанию",
"settingQuickSettingSidebarDefaultWidthDesc": "Ширина боковой панели по умолчанию при запуске",
"settingQuickSettingSidebarDisplayMode": "Режим отображения",
"settingQuickSettingSidebarDisplayModeDesc": "Фиксирован постоянно или появляется при наведении на боковую панель",
"settingQuickSettingSidebarEnable": "Включено",
"settingQuickSettingSidebarEnableDesc": "Включить боковую панель быстрых настроек в левой части экрана",
"settingReduceAnimation": "Уменьшить анимации",
"settingReduceAnimationDesc": "Уменьшить эффект размытия и цвет фоновой подсветки, что позволит повысить плавность работы и снизить нагрузку на процессор",
"settingSplitPreviewer": "Рзделенный просмоторщик",
"settingSplitPreviewerDesc": "Разделяет страницу на настройки генрации и поле ввода промта, а на второй половине остается окно сгенерированной картинки и кнопка генерации ",
"settingSvgIcons": "Использование SVG-значков",
"settingSvgIconsDesc": "Заменить все иконки Emoji в стабильном diffusion webui на SVG-иконки в глобальном масштабе",
"switchTheme": "Переключатель светлой/темной темы",
"sync": "Синхронизация с настройками webui",
"themeFeedback": "Отзыв о теме",
"themeSetting": "Настройки темы"
"brand": {
"kitchen": "Kitchen",
"lobe": "LobeHub",
"custom": "Пользовательский"
},
"custom": {
"initializing": "StableDiffusion / LobeTheme инициализируется, пожалуйста, подождите..."
},
"footer": {
"resources": "Ресурсы",
"community": "Сообщество",
"help": "Помощь",
"moreProducts": "Дополнительные продукты"
},
"header": {
"feedback": "Обратная связь",
"switchTheme": "Переключить светлую/темную тему",
"setting": "Настройка"
},
"modal": {
"themeFeedback": {
"title": "Обратная связь по теме"
},
"themeSetting": {
"title": "Настройки темы"
}
},
"prompt": {
"load": "Загрузить подсказку",
"set": "Установить подсказку",
"negative": "Негативный",
"positive": "Позитивный"
},
"setting": {
"button": {
"reset": "Сброс",
"submit": "Применить и перезапустить интерфейс"
},
"confirmPageUnload": {
"title": "Подтверждение покидания страницы",
"desc": "Помогает предотвратить потерю несохраненных данных"
},
"customFont": {
"title": "Пользовательский шрифт",
"desc": "При включении автоматически загружает веб-шрифт для улучшения отображения текста на китайском, английском и коде"
},
"customLogo": {
"title": "Пользовательский логотип",
"desc": "Поддерживает URL / Base64 / символы Emoji"
},
"customTitle": {
"title": "Пользовательское название",
"desc": "Пользовательское название логотипа"
},
"extraNetworkSidebar": {
"defaultCardSize": {
"title": "Размер обложки модели",
"desc": "Значение по умолчанию размера обложки модели при запуске"
},
"defaultExpand": {
"title": "Раскрыть по умолчанию",
"desc": "Развернуть боковую панель по умолчанию при запуске"
},
"defaultWidth": {
"title": "Ширина по умолчанию",
"desc": "Ширина боковой панели по умолчанию при запуске"
},
"displayMode": {
"title": "Режим отображения",
"desc": "Фиксированный режим сетки для постоянного отображения, автоматическое развертывание при перемещении мыши к боковой стороне в плавающем режиме"
},
"enable": {
"title": "Включить",
"desc": "Включить дополнительную боковую панель сети справа"
}
},
"group": {
"extraNetworkSidebar": "Дополнительная боковая панель сети",
"layout": "Настройки макета",
"promptTextarea": "Текстовое поле подсказки",
"quickSettingSidebar": "Быстрая боковая панель настроек",
"theme": "Настройки темы"
},
"hideFooter": {
"title": "Скрыть нижний колонтитул",
"desc": "Скрыть нижний колонтитул темы и отобразить только стандартный колонтитул стабильного веб-интерфейса диффузии"
},
"language": {
"title": "Язык",
"desc": "Язык темы Lobe"
},
"logoType": {
"title": "Тип логотипа",
"desc": "Тип логотипа",
"preview": "Предварительный просмотр"
},
"neutralColor": {
"title": "Нейтральный цвет",
"desc": "Настроить различные оттенки серого с различными цветовыми тенденциями, второй - это оригинальный нейтральный цвет кухни"
},
"primaryColor": {
"title": "Основной цвет",
"desc": "Пользовательский основной цвет, второй - это оригинальный цвет темы кухни"
},
"promptDisplayMode": {
"title": "Режим отображения подсказки",
"desc": "Фиксированная высота или автоматическая высота с поддержкой изменения размера",
"resizable": "Изменяемый размер",
"scroll": "Прокрутка"
},
"promptEditor": {
"title": "Редактор подсказки",
"desc": "Предоставляет простой редактор подсказок в верхней части быстрой боковой панели настроек"
},
"promptHighlight": {
"title": "Подсветка синтаксиса подсказки",
"desc": "Автоматическое окрашивание отображения подсказок в соответствии с правилами синтаксиса Stable Diffusion"
},
"quickSettingSidebar": {
"defaultExpand": {
"title": "Раскрыть по умолчанию",
"desc": "Развернуть боковую панель по умолчанию при запуске"
},
"defaultWidth": {
"title": "Ширина по умолчанию",
"desc": "Ширина боковой панели по умолчанию при запуске"
},
"displayMode": {
"title": "Режим отображения",
"desc": "Фиксированный режим сетки для постоянного отображения, автоматическое развертывание при перемещении мыши к боковой стороне в плавающем режиме"
},
"enable": {
"title": "Включить",
"desc": "Включить быструю боковую панель настроек слева"
}
},
"reduceAnimation": {
"title": "Уменьшить анимацию",
"desc": "Уменьшить эффект размытия и цвет фона, что может улучшить плавность и экономить использование ЦП"
},
"splitPreviewer": {
"title": "Разделить предварительный просмотр",
"desc": "Разместить поле ввода подсказки слева и кнопку генерации справа, обеспечивая отображение сгенерированного изображения всегда вверху при прокрутке (экспериментально)"
},
"svgIcons": {
"title": "SVG-иконы",
"desc": "Заменить все иконки Emoji в стабильном веб-интерфейсе диффузии на глобальные SVG-иконы"
}
},
"sidebar": {
"extraNetwork": "Дополнительная сеть",
"quickSetting": "Быстрая настройка",
"mode": {
"fixed": "Фиксированный",
"float": "Плавающий"
}
}
}

View File

@ -1,82 +1,159 @@
{
"appInitializing": "StableDiffusion / LobeTheme 启动中,请耐心等待...",
"community": "社区",
"custom": "自定义",
"extraNetwork": "附加网络",
"feedback": "反馈",
"fixed": "固定",
"float": "悬浮",
"help": "帮助",
"kitchen": "Kitchen",
"loadPrompt": "加载提示",
"lobe": "Lobe",
"moreProducts": "更多产品",
"negative": "反向提示词",
"positive": "正面提示词",
"quickSetting": "快捷设置",
"resizable": "缩放",
"resources": "相关资源",
"scroll": "滚动",
"setPrompt": "发送提示词",
"setting": "设置",
"settingButtonReset": "重置",
"settingButtonSubmit": "应用并重启界面",
"settingConfirmPageUnload": "确认离开页面",
"settingConfirmPageUnloadDesc": "有助于防止丢失未保存的数据",
"settingCustomFont": "加载字体美化",
"settingCustomFontDesc": "开启后会自动加载 Webfont 美化字体,优化中英文和代码显示效果",
"settingCustomLogo": "自定义 Logo",
"settingCustomLogoDesc": "支持 URL / Base64 / Emoji 表情符号",
"settingCustomTitle": "自定义标题",
"settingCustomTitleDesc": "自定义 Logo 标题名称",
"settingExtraNetworkSidebarDefaultCardSize": "模型封面尺寸",
"settingExtraNetworkSidebarDefaultCardSizeDesc": "启动时模型封面尺寸默认值",
"settingExtraNetworkSidebarDefaultExpand": "默认展开",
"settingExtraNetworkSidebarDefaultExpandDesc": "是否在启动时将侧边栏默认展开",
"settingExtraNetworkSidebarDefaultWidth": "默认宽度",
"settingExtraNetworkSidebarDefaultWidthDesc": "侧边栏在启动时的默认宽度",
"settingExtraNetworkSidebarDisplayMode": "显示模式",
"settingExtraNetworkSidebarDisplayModeDesc": "固定为栅格模式常驻显示,悬浮模式时当鼠标移到侧边时自动展开",
"settingExtraNetworkSidebarEnable": "启用",
"settingExtraNetworkSidebarEnableDesc": "启用位于右侧的附加网络侧边栏",
"settingGroupExtraNetworkSidebar": "附加网络侧边栏",
"settingGroupLayout": "布局设置",
"settingGroupPromptTextarea": "提示词文本框",
"settingGroupQuickSettingSidebar": "快捷设置侧边栏",
"settingGroupTheme": "主题设置",
"settingHideFooter": "隐藏页脚",
"settingHideFooterDesc": "隐藏主题页脚,只显示 stable diffusion webui 默认页脚",
"settingLanguage": "语言",
"settingLanguageDesc": "Lobe Theme 主题语言",
"settingLogoPreview": "预览",
"settingLogoType": "Logo 类型",
"settingLogoTypeDesc": "Logo 类型",
"settingNeutralColor": "中性色",
"settingNeutralColorDesc": "不同色彩倾向的灰阶自定义,第二个为原始 Kitchen 中性色",
"settingPrimaryColor": "主题色",
"settingPrimaryColorDesc": "自定义主题色,第二个为原始 Kitchen 主题色",
"settingPromptDisplayMode": "显示模式",
"settingPromptDisplayModeDesc": "固定高度或自动高度并支持拖拽拉伸",
"settingPromptEditor": "提示词编辑器",
"settingPromptEditorDesc": "提供简易的提示词编辑器位于快捷设置侧边栏顶部",
"settingPromptHighlight": "Prompt 语法高亮",
"settingPromptHighlightDesc": "按 Stable Diffusion 语法规则,自动染色 prompt 显示",
"settingQuickSettingSidebarDefaultExpand": "默认展开",
"settingQuickSettingSidebarDefaultExpandDesc": "是否在启动时将侧边栏默认展开",
"settingQuickSettingSidebarDefaultWidth": "默认宽度",
"settingQuickSettingSidebarDefaultWidthDesc": "侧边栏在启动时的默认宽度",
"settingQuickSettingSidebarDisplayMode": "显示模式",
"settingQuickSettingSidebarDisplayModeDesc": "固定为栅格模式常驻显示,悬浮模式时当鼠标移到侧边时自动展开",
"settingQuickSettingSidebarEnable": "启用",
"settingQuickSettingSidebarEnableDesc": "启用位于左侧的快捷设置侧边栏",
"settingReduceAnimation": "减少动画效果",
"settingReduceAnimationDesc": "减少毛玻璃效果和背景流动色,可以提升流畅度并节省 CPU 使用",
"settingSplitPreviewer": "双列模式",
"settingSplitPreviewerDesc": "将提示词输入框放在左侧,生成按钮于右侧,确保在滚动时生成的图像始终显示在顶部 (实验性)",
"settingSvgIcons": "使用 SVG 图标",
"settingSvgIconsDesc": "将 stable diffusion webui 中的 Emoji 图标全局替换为 SVG 图标",
"switchTheme": "切换亮暗色主题",
"sync": "与 WebUI 设置同步",
"themeFeedback": "主题反馈",
"themeSetting": "主题设置"
"brand": {
"kitchen": "Kitchen",
"lobe": "LobeHub",
"custom": "自定义"
},
"custom": {
"initializing": "StableDiffusion / LobeTheme 正在初始化,请稍候..."
},
"footer": {
"resources": "资源",
"community": "社区",
"help": "帮助",
"moreProducts": "更多产品"
},
"header": {
"feedback": "反馈",
"switchTheme": "切换浅色/深色主题",
"setting": "设置"
},
"modal": {
"themeFeedback": {
"title": "主题反馈"
},
"themeSetting": {
"title": "主题设置"
}
},
"prompt": {
"load": "加载提示",
"set": "设置提示",
"negative": "否定",
"positive": "肯定"
},
"setting": {
"button": {
"reset": "重置",
"submit": "应用并重新启动界面"
},
"confirmPageUnload": {
"title": "确认离开页面",
"desc": "帮助防止未保存数据的丢失"
},
"customFont": {
"title": "自定义字体",
"desc": "启用后,将自动加载网页字体以增强中文、英文和代码的显示"
},
"customLogo": {
"title": "自定义标志",
"desc": "支持 URL / Base64 / 表情符号"
},
"customTitle": {
"title": "自定义标题",
"desc": "自定义标志标题"
},
"extraNetworkSidebar": {
"defaultCardSize": {
"title": "模型封面大小",
"desc": "启动时模型封面大小的默认值"
},
"defaultExpand": {
"title": "默认展开",
"desc": "启动时默认是否展开侧边栏"
},
"defaultWidth": {
"title": "默认宽度",
"desc": "启动时侧边栏的默认宽度"
},
"displayMode": {
"title": "显示模式",
"desc": "固定网格模式以保持恒定显示,在浮动模式下鼠标移至侧边时自动展开"
},
"enable": {
"title": "启用",
"desc": "在右侧启用额外的网络侧边栏"
}
},
"group": {
"extraNetworkSidebar": "额外网络侧边栏",
"layout": "布局设置",
"promptTextarea": "提示文本框",
"quickSettingSidebar": "快速设置侧边栏",
"theme": "主题设置"
},
"hideFooter": {
"title": "隐藏页脚",
"desc": "隐藏主题页脚,仅显示 Stable Diffusion webui 的默认页脚"
},
"language": {
"title": "语言",
"desc": "叶片主题语言"
},
"logoType": {
"title": "标志类型",
"desc": "标志类型",
"preview": "预览"
},
"neutralColor": {
"title": "中性颜色",
"desc": "定制不同色调的灰色,第二个是原始厨房中性颜色"
},
"primaryColor": {
"title": "主色",
"desc": "自定义主色,第二个是原始厨房主题颜色"
},
"promptDisplayMode": {
"title": "提示显示模式",
"desc": "固定高度或可拖动调整大小的自动高度",
"resizable": "可调整大小",
"scroll": "滚动"
},
"promptEditor": {
"title": "提示编辑器",
"desc": "在快速设置侧边栏顶部提供简单的提示编辑器"
},
"promptHighlight": {
"title": "提示语法高亮",
"desc": "根据 Stable Diffusion 语法规则自动着色提示显示"
},
"quickSettingSidebar": {
"defaultExpand": {
"title": "默认展开",
"desc": "启动时默认是否展开侧边栏"
},
"defaultWidth": {
"title": "默认宽度",
"desc": "启动时侧边栏的默认宽度"
},
"displayMode": {
"title": "显示模式",
"desc": "固定网格模式以保持恒定显示,在浮动模式下鼠标移至侧边时自动展开"
},
"enable": {
"title": "启用",
"desc": "在左侧启用快速设置侧边栏"
}
},
"reduceAnimation": {
"title": "减少动画",
"desc": "减少模糊效果和背景流动颜色,可提高流畅度并节省 CPU 使用率"
},
"splitPreviewer": {
"title": "分割预览器",
"desc": "将提示输入框放在左侧,生成按钮放在右侧,确保滚动时生成的图像始终显示在顶部(实验性)"
},
"svgIcons": {
"title": "SVG 图标",
"desc": "全局替换 Stable Diffusion webui 中的所有表情符号为 SVG 图标"
}
},
"sidebar": {
"extraNetwork": "额外网络",
"quickSetting": "快速设置",
"mode": {
"fixed": "固定",
"float": "浮动"
}
}
}

View File

@ -1,82 +1,159 @@
{
"appInitializing": "StableDiffusion / LobeTheme 正在初始化,请稍候...",
"community": "社區",
"custom": "自訂",
"extraNetwork": "附加網絡",
"feedback": "反饋",
"fixed": "固定",
"float": "懸浮",
"help": "幫助",
"kitchen": "Kitchen",
"loadPrompt": "加載提示",
"lobe": "Lobe",
"moreProducts": "更多產品",
"negative": "反向提示詞",
"positive": "正面提示詞",
"quickSetting": "快捷設置",
"resizable": "縮放",
"resources": "相關資源",
"scroll": "滾動",
"setPrompt": "發送提示詞",
"setting": "設置",
"settingButtonReset": "重置",
"settingButtonSubmit": "應用並重啟界面",
"settingConfirmPageUnload": "確認離開頁面",
"settingConfirmPageUnloadDesc": "有助於防止遺失未保存的數據",
"settingCustomFont": "載入字型美化",
"settingCustomFontDesc": "開啟後會自動載入 Webfont 美化字型,優化中英文和程式碼顯示效果",
"settingCustomLogo": "自定義 Logo",
"settingCustomLogoDesc": "支持 URL / Base64 / Emoji 表情符號",
"settingCustomTitle": "自定義標題",
"settingCustomTitleDesc": "自定義 Logo 標題名稱",
"settingExtraNetworkSidebarDefaultCardSize": "模型封面尺寸",
"settingExtraNetworkSidebarDefaultCardSizeDesc": "啟動時模型封面尺寸默認值",
"settingExtraNetworkSidebarDefaultExpand": "默認展開",
"settingExtraNetworkSidebarDefaultExpandDesc": "是否在啟動時將側邊欄默認展開",
"settingExtraNetworkSidebarDefaultWidth": "默認寬度",
"settingExtraNetworkSidebarDefaultWidthDesc": "側邊欄在啟動時的默認寬度",
"settingExtraNetworkSidebarDisplayMode": "顯示模式",
"settingExtraNetworkSidebarDisplayModeDesc": "固定為格模式常駐顯示,懸浮模式時當鼠標移到側邊時自動展開",
"settingExtraNetworkSidebarEnable": "啟用",
"settingExtraNetworkSidebarEnableDesc": "啟用位於右側的附加網絡側邊欄",
"settingGroupExtraNetworkSidebar": "附加網絡側邊欄",
"settingGroupLayout": "佈局設置",
"settingGroupPromptTextarea": "提示詞文本框",
"settingGroupQuickSettingSidebar": "快捷設置側邊欄",
"settingGroupTheme": "主題設置",
"settingHideFooter": "隱藏頁腳",
"settingHideFooterDesc": "隱藏主題頁腳,只顯示 stable diffusion webui 默認頁腳",
"settingLanguage": "語言",
"settingLanguageDesc": "Lobe Theme 主題語言",
"settingLogoPreview": "預覽",
"settingLogoType": "Logo 類型",
"settingLogoTypeDesc": "Logo 類型",
"settingNeutralColor": "中性色",
"settingNeutralColorDesc": "不同色彩傾向的灰階自定義,第二個為原始 Kitchen 中性色",
"settingPrimaryColor": "主題色",
"settingPrimaryColorDesc": "自定義主題色,第二個為原始 Kitchen 主題色",
"settingPromptDisplayMode": "顯示模式",
"settingPromptDisplayModeDesc": "固定高度或自動高度並支持拖拽拉伸",
"settingPromptEditor": "提示詞編輯器",
"settingPromptEditorDesc": "提供簡易的提示詞編輯器位於快捷設置側邊欄頂部",
"settingPromptHighlight": "Prompt 語法高亮",
"settingPromptHighlightDesc": "按照 Stable Diffusion 語法規則,自動著色 prompt 顯示",
"settingQuickSettingSidebarDefaultExpand": "默認展開",
"settingQuickSettingSidebarDefaultExpandDesc": "是否在啟動時將側邊欄默認展開",
"settingQuickSettingSidebarDefaultWidth": "默認寬度",
"settingQuickSettingSidebarDefaultWidthDesc": "側邊欄在啟動時的默認寬度",
"settingQuickSettingSidebarDisplayMode": "顯示模式",
"settingQuickSettingSidebarDisplayModeDesc": "固定為格模式常駐顯示,懸浮模式時當鼠標移到側邊時自動展開",
"settingQuickSettingSidebarEnable": "啟用",
"settingQuickSettingSidebarEnableDesc": "啟用位於左側的快捷設置側邊欄",
"settingReduceAnimation": "減少動畫效果",
"settingReduceAnimationDesc": "減少毛玻璃效果和背景流動色,可以提升流暢度並節省 CPU 使用",
"settingSplitPreviewer": "雙列模式",
"settingSplitPreviewerDesc": "將提示詞輸入框放在左側,生成按鈕於右側,確保在滾動時生成的圖像始終顯示在頂部 (實驗性)",
"settingSvgIcons": "使用 SVG 圖標",
"settingSvgIconsDesc": "將 stable diffusion webui 中的 Emoji 圖標全局替換為 SVG 圖標",
"switchTheme": "切換亮暗色主題",
"sync": "與 WebUI 設置同步",
"themeFeedback": "主題反饋",
"themeSetting": "主題設置"
"brand": {
"kitchen": "Kitchen",
"lobe": "LobeHub",
"custom": "自定義"
},
"custom": {
"initializing": "StableDiffusion / LobeTheme 正在初始化,請稍候..."
},
"footer": {
"resources": "資源",
"community": "社區",
"help": "幫助",
"moreProducts": "更多產品"
},
"header": {
"feedback": "反饋",
"switchTheme": "切換明/暗主題",
"setting": "設置"
},
"modal": {
"themeFeedback": {
"title": "主題反饋"
},
"themeSetting": {
"title": "主題設置"
}
},
"prompt": {
"load": "載入提示",
"set": "設置提示",
"negative": "否定",
"positive": "肯定"
},
"setting": {
"button": {
"reset": "重置",
"submit": "應用並重新啟動界面"
},
"confirmPageUnload": {
"title": "離開頁面確認",
"desc": "幫助防止未保存數據的丟失"
},
"customFont": {
"title": "自定義字體",
"desc": "啟用後,將自動加載網頁字體,以增強中文、英文和代碼的顯示效果"
},
"customLogo": {
"title": "自定義標誌",
"desc": "支援 URL / Base64 / 表情符號"
},
"customTitle": {
"title": "自定義標題",
"desc": "自定義標誌標題"
},
"extraNetworkSidebar": {
"defaultCardSize": {
"title": "模型封面大小",
"desc": "啟動時的模型封面大小的默認值"
},
"defaultExpand": {
"title": "默認展開",
"desc": "啟動時是否默認展開側邊欄"
},
"defaultWidth": {
"title": "默認寬度",
"desc": "啟動時側邊欄的默認寬度"
},
"displayMode": {
"title": "顯示模式",
"desc": "固定為網格模式以保持恆定顯示,在浮動模式下當滑鼠移至側邊時自動展開"
},
"enable": {
"title": "啟用",
"desc": "在右側啟用額外的網絡側邊欄"
}
},
"group": {
"extraNetworkSidebar": "額外網絡側邊欄",
"layout": "佈局設置",
"promptTextarea": "提示文本框",
"quickSettingSidebar": "快速設置側邊欄",
"theme": "主題設置"
},
"hideFooter": {
"title": "隱藏頁腳",
"desc": "隱藏主題頁腳,僅顯示 Stable Diffusion webui 的默認頁腳"
},
"language": {
"title": "語言",
"desc": "Lobe 主題語言"
},
"logoType": {
"title": "標誌類型",
"desc": "標誌類型",
"preview": "預覽"
},
"neutralColor": {
"title": "中性顏色",
"desc": "使用不同的色調自定義不同的灰色,第二個是原始 Kitchen 中性顏色"
},
"primaryColor": {
"title": "主要顏色",
"desc": "自定義主要顏色,第二個是原始 Kitchen 主題顏色"
},
"promptDisplayMode": {
"title": "提示顯示模式",
"desc": "固定高度或可拖動調整大小的自動高度",
"resizable": "可調整大小",
"scroll": "滾動"
},
"promptEditor": {
"title": "提示編輯器",
"desc": "在快速設置側邊欄頂部提供簡單的提示編輯器"
},
"promptHighlight": {
"title": "提示語法高亮",
"desc": "根據 Stable Diffusion 語法規則自動著色提示顯示"
},
"quickSettingSidebar": {
"defaultExpand": {
"title": "默認展開",
"desc": "啟動時是否默認展開側邊欄"
},
"defaultWidth": {
"title": "默認寬度",
"desc": "啟動時側邊欄的默認寬度"
},
"displayMode": {
"title": "顯示模式",
"desc": "固定為網格模式以保持恆定顯示,在浮動模式下當滑鼠移至側邊時自動展開"
},
"enable": {
"title": "啟用",
"desc": "在左側啟用快速設置側邊欄"
}
},
"reduceAnimation": {
"title": "減少動畫",
"desc": "減少模糊效果和背景流動顏色,可提高流暢度並節省 CPU 使用率"
},
"splitPreviewer": {
"title": "分割預覽器",
"desc": "將提示輸入框放在左側,生成按鈕放在右側,確保滾動時始終在頂部顯示生成的圖像(實驗性)"
},
"svgIcons": {
"title": "SVG 圖示",
"desc": "全局替換 Stable Diffusion webui 中的所有表情符號為 SVG 圖示"
}
},
"sidebar": {
"extraNetwork": "額外網絡",
"quickSetting": "快速設置",
"mode": {
"fixed": "固定",
"float": "浮動"
}
}
}

View File

@ -70,6 +70,7 @@
"ahooks": "^3",
"antd": "^5",
"antd-style": "latest",
"consola": "^3.2.3",
"i18next": "^23",
"i18next-http-backend": "^2",
"lodash-es": "^4",

View File

@ -1,3 +1,5 @@
import { consola } from 'consola';
import RefreshRuntime from '/@react-refresh';
const RefreshSig = (type) => type;
@ -7,4 +9,4 @@ window.$RefreshReg$ = () => {};
window.$RefreshSig$ = () => RefreshSig;
window.__vite_plugin_react_preamble_installed__ = true;
console.debug('🤯 Injecting React Refresh');
consola.success('🤯 Injecting React Refresh');

View File

@ -18,51 +18,51 @@ import { useStyles } from './style';
const HEADER_HEIGHT = 64;
const Index = memo(() => {
const setting = useAppStore(selectors.currentSetting, isEqual);
const { cx, styles } = useStyles({
headerHeight: HEADER_HEIGHT,
isPrimaryColor: Boolean(setting.primaryColor),
});
const setting = useAppStore(selectors.currentSetting, isEqual);
const { cx, styles } = useStyles({
headerHeight: HEADER_HEIGHT,
isPrimaryColor: Boolean(setting.primaryColor),
});
useEffect(() => {
if (setting.enableHighlight) {
PromptHighlight('#txt2img_prompt', '#lobe_txt2img_prompt');
PromptHighlight('#img2img_prompt', '#lobe_img2img_prompt');
}
if (setting.svgIcon) replaceIcon();
}, []);
useEffect(() => {
if (setting.enableHighlight) {
PromptHighlight('#txt2img_prompt', '#lobe_txt2img_prompt');
PromptHighlight('#img2img_prompt', '#lobe_img2img_prompt');
}
if (setting.svgIcon) replaceIcon();
}, []);
return (
<>
<GlobalStyle />
<LayoutHeader headerHeight={HEADER_HEIGHT}>
<Header />
</LayoutHeader>
<LayoutMain>
{<div className={setting.liteAnimation ? styles.backgroundLite : styles.background} />}
{setting.enableSidebar && (
<LayoutSidebar
className={styles.sidebar}
headerHeight={HEADER_HEIGHT}
style={{ flex: 0, zIndex: 50 }}
>
<QuickSettingSidebar headerHeight={HEADER_HEIGHT} />
</LayoutSidebar>
)}
<Content className={cx(!setting.enableSidebar && styles.quicksettings)} />
{setting?.enableExtraNetworkSidebar && (
<LayoutSidebar
className={styles.sidebar}
headerHeight={HEADER_HEIGHT}
style={{ flex: 0, zIndex: 50 }}
>
<ExtraNetworkSidebar headerHeight={HEADER_HEIGHT} />
</LayoutSidebar>
)}
</LayoutMain>
<Footer />
</>
);
return (
<>
<GlobalStyle />
<LayoutHeader headerHeight={HEADER_HEIGHT}>
<Header />
</LayoutHeader>
<LayoutMain>
{<div className={setting.liteAnimation ? styles.backgroundLite : styles.background} />}
{setting.enableSidebar && (
<LayoutSidebar
className={styles.sidebar}
headerHeight={HEADER_HEIGHT}
style={{ flex: 0, zIndex: 50 }}
>
<QuickSettingSidebar headerHeight={HEADER_HEIGHT} />
</LayoutSidebar>
)}
<Content className={cx(!setting.enableSidebar && styles.quicksettings)} />
{setting?.enableExtraNetworkSidebar && (
<LayoutSidebar
className={styles.sidebar}
headerHeight={HEADER_HEIGHT}
style={{ flex: 0, zIndex: 50 }}
>
<ExtraNetworkSidebar headerHeight={HEADER_HEIGHT} />
</LayoutSidebar>
)}
</LayoutMain>
<Footer />
</>
);
});
export default Index;

View File

@ -1,3 +1,4 @@
import { consola } from 'consola';
import { PropsWithChildren, Suspense, memo, useEffect, useState } from 'react';
import { Helmet } from 'react-helmet';
import { shallow } from 'zustand/shallow';
@ -9,61 +10,60 @@ import { useAppStore } from '@/store';
import manifest from './manifest';
export const Layouts = memo<PropsWithChildren>(({ children }) => {
const [loading, setLoading] = useState(true);
const { setCurrentTab, onInit, storeLoading } = useAppStore(
(st) => ({
onInit: st.onInit,
setCurrentTab: st.setCurrentTab,
storeLoading: st.loading,
}),
shallow,
);
const [loading, setLoading] = useState(true);
const { setCurrentTab, onInit, storeLoading } = useAppStore(
(st) => ({
onInit: st.onInit,
setCurrentTab: st.setCurrentTab,
storeLoading: st.loading,
}),
shallow,
);
useEffect(() => {
console.time('🤯 Lobe Theme loading');
onInit();
onUiLoaded(() => {
setLoading(false);
console.timeEnd('🤯 Lobe Theme loading');
});
onUiTabChange(() => {
setCurrentTab();
});
}, []);
useEffect(() => {
onInit();
onUiLoaded(() => {
setLoading(false);
consola.success('🤯 Lobe Theme loading');
});
onUiTabChange(() => {
setCurrentTab();
});
}, []);
return (
<Suspense fallback="loading...">
<Helmet>
<link
href="https://registry.npmmirror.com/@lobehub/assets-favicons/1.1.0/files/assets/apple-touch-icon.png"
rel="apple-touch-icon"
sizes="180x180"
/>
<link
href="https://registry.npmmirror.com/@lobehub/assets-favicons/1.1.0/files/assets/favicon-32x32.png"
rel="icon"
sizes="32x32"
type="image/png"
/>
<link
href="https://registry.npmmirror.com/@lobehub/assets-favicons/1.1.0/files/assets/favicon-16x16.png"
rel="icon"
sizes="16x16"
type="image/png"
/>
<link
href="https://registry.npmmirror.com/@lobehub/assets-favicons/1.1.0/files/assets/site.webmanifest"
rel="manifest"
/>
<meta content="Stable Diffusion · LobeHub" name="apple-mobile-web-app-title" />
<meta content="Stable Diffusion · LobeHub" name="application-name" />
<meta content="#000000" name="msapplication-TileColor" />
<meta content="#000000" name="theme-color" />
<link href={manifest} rel="manifest" />
</Helmet>
<Layout>{storeLoading === false && loading === false ? children : <Loading />}</Layout>
</Suspense>
);
return (
<Suspense fallback="loading...">
<Helmet>
<link
href="https://registry.npmmirror.com/@lobehub/assets-favicons/1.1.0/files/assets/apple-touch-icon.png"
rel="apple-touch-icon"
sizes="180x180"
/>
<link
href="https://registry.npmmirror.com/@lobehub/assets-favicons/1.1.0/files/assets/favicon-32x32.png"
rel="icon"
sizes="32x32"
type="image/png"
/>
<link
href="https://registry.npmmirror.com/@lobehub/assets-favicons/1.1.0/files/assets/favicon-16x16.png"
rel="icon"
sizes="16x16"
type="image/png"
/>
<link
href="https://registry.npmmirror.com/@lobehub/assets-favicons/1.1.0/files/assets/site.webmanifest"
rel="manifest"
/>
<meta content="Stable Diffusion · LobeHub" name="apple-mobile-web-app-title" />
<meta content="Stable Diffusion · LobeHub" name="application-name" />
<meta content="#000000" name="msapplication-TileColor" />
<meta content="#000000" name="theme-color" />
<link href={manifest} rel="manifest" />
</Helmet>
<Layout>{storeLoading === false && loading === false ? children : <Loading />}</Layout>
</Suspense>
);
});
export default Layouts;

View File

@ -1,28 +1,28 @@
const manifest = {
background_color: '#000000',
description:
background_color: '#000000',
description:
'The modern theme for stable diffusion webui, exquisite interface design, highly customizable UI, and efficiency boosting features.',
display: 'standalone',
icons: [
{
sizes: '192x192',
src: 'https://registry.npmmirror.com/@lobehub/assets-favicons/1.1.0/files/assets/android-chrome-192x192.png',
type: 'image/png',
},
{
sizes: '512x512',
src: 'https://registry.npmmirror.com/@lobehub/assets-favicons/1.1.0/files/assets/android-chrome-512x512.png',
type: 'image/png',
},
],
id: '/',
name: 'Stable Diffusion',
orientation: 'portrait',
scope: '/',
short_name: 'Stable Diffusion',
splash_pages: null,
start_url: location.origin,
theme_color: '#000000',
display: 'standalone',
icons: [
{
sizes: '192x192',
src: 'https://registry.npmmirror.com/@lobehub/assets-favicons/1.1.0/files/assets/android-chrome-192x192.png',
type: 'image/png',
},
{
sizes: '512x512',
src: 'https://registry.npmmirror.com/@lobehub/assets-favicons/1.1.0/files/assets/android-chrome-512x512.png',
type: 'image/png',
},
],
id: '/',
name: 'Stable Diffusion',
orientation: 'portrait',
scope: '/',
short_name: 'Stable Diffusion',
splash_pages: null,
start_url: location.origin,
theme_color: '#000000',
};
export default `data:application/manifest+json;base64,${btoa(JSON.stringify(manifest))}`;

View File

@ -2,9 +2,9 @@ import Index from './index';
import Layout from './layout';
export default () => {
return (
<Layout>
<Index />
</Layout>
);
return (
<Layout>
<Index />
</Layout>
);
};

View File

@ -2,13 +2,13 @@ import { createStyles } from 'antd-style';
import { adjustHue } from 'polished';
export const useStyles = createStyles(
(
{ cx, css, stylish, token, isDarkMode },
{ headerHeight, isPrimaryColor }: { headerHeight: number; isPrimaryColor: boolean },
) => ({
background: cx(
stylish.gradientAnimation,
isPrimaryColor &&
(
{ cx, css, stylish, token, isDarkMode },
{ headerHeight, isPrimaryColor }: { headerHeight: number; isPrimaryColor: boolean },
) => ({
background: cx(
stylish.gradientAnimation,
isPrimaryColor &&
css`
background-image: linear-gradient(
-45deg,
@ -18,7 +18,7 @@ export const useStyles = createStyles(
${adjustHue(-45, token.colorPrimary)}
);
`,
css`
css`
pointer-events: none;
position: absolute !important;
@ -32,8 +32,8 @@ export const useStyles = createStyles(
opacity: 0.2;
filter: blur(100px);
`,
),
backgroundLite: css`
),
backgroundLite: css`
pointer-events: none;
position: absolute !important;
@ -50,19 +50,19 @@ export const useStyles = createStyles(
transparent
);
`,
panel: css`
panel: css`
.draggable-panel {
border-style: dashed;
}
`,
quicksettings: css`
quicksettings: css`
#quicksettings {
align-items: start;
padding: 16px !important;
}
`,
sidebar: css`
sidebar: css`
height: calc(100vh - ${headerHeight}px);
`,
}),
}),
);

View File

@ -1,11 +1,11 @@
import {
ActionIcon,
DiscordIcon,
Giscus as G,
GradientButton,
Icon,
Modal,
type ModalProps,
ActionIcon,
DiscordIcon,
Giscus as G,
GradientButton,
Icon,
Modal,
type ModalProps,
} from '@lobehub/ui';
import { Button, Space } from 'antd';
import { useTheme } from 'antd-style';
@ -27,49 +27,49 @@ export interface GiscusProps {
const repoName = homepage.replace('https://github.com/', '') as `${string}/${string}`;
const Giscus = memo<GiscusProps>(({ open, onCancel }) => {
const setting = useAppStore(selectors.currentSetting, isEqual);
const theme = useTheme();
const { t } = useTranslation();
return (
<Modal
footer={false}
onCancel={onCancel}
open={open}
title={
<Flexbox align={'center'} gap={4} horizontal>
<a href={'https://discord.gg/AYFPHvv2jT'} rel="noreferrer" target="_blank">
<ActionIcon icon={DiscordIcon} title={'Discord'} />
</a>
<a href={homepage} rel="noreferrer" target="_blank">
<ActionIcon icon={Github} title={repoName} />
</a>
<Space>
{t('themeFeedback')}
<VersionTag />
</Space>
</Flexbox>
}
>
<Flexbox gap={32}>
<Center
gap={16}
horizontal
style={{
background: theme.colorBgLayout,
border: `1px solid ${theme.colorBorderSecondary}`,
borderRadius: theme.borderRadiusLG,
padding: '16px 0',
}}
const setting = useAppStore(selectors.currentSetting, isEqual);
const theme = useTheme();
const { t } = useTranslation();
return (
<Modal
footer={false}
onCancel={onCancel}
open={open}
title={
<Flexbox align={'center'} gap={4} horizontal>
<a href={'https://discord.gg/AYFPHvv2jT'} rel="noreferrer" target="_blank">
<ActionIcon icon={DiscordIcon} title={'Discord'} />
</a>
<a href={homepage} rel="noreferrer" target="_blank">
<ActionIcon icon={Github} title={repoName} />
</a>
<Space>
{t('modal.themeFeedback.title')}
<VersionTag />
</Space>
</Flexbox>
}
>
<Button icon={<Icon icon={DiscordIcon} />} size={'large'}>
<Flexbox gap={32}>
<Center
gap={16}
horizontal
style={{
background: theme.colorBgLayout,
border: `1px solid ${theme.colorBorderSecondary}`,
borderRadius: theme.borderRadiusLG,
padding: '16px 0',
}}
>
<Button icon={<Icon icon={DiscordIcon} />} size={'large'}>
Join Discover
</Button>
<GradientButton icon={<Icon icon={Github} />}>LobeTheme Github</GradientButton>
</Center>
<G lang={setting.i18n} mapping="number" repo={repoName} repoId="R_kgDOJCPcNg" term="53" />
</Flexbox>
</Modal>
);
</Button>
<GradientButton icon={<Icon icon={Github} />}>LobeTheme Github</GradientButton>
</Center>
<G lang={setting.i18n} mapping="number" repo={repoName} repoId="R_kgDOJCPcNg" term="53" />
</Flexbox>
</Modal>
);
});
export default Giscus;

View File

@ -5,19 +5,19 @@ import { useTranslation } from 'react-i18next';
import { Center, Flexbox } from 'react-layout-kit';
const Loading = memo(() => {
const { t } = useTranslation();
const { t } = useTranslation();
return (
<Flexbox height={'100vh'} width={'100%'}>
<Center flex={1} gap={12} width={'100%'}>
<Logo extra={'SD'} size={48} type={'combine'} />
<Center gap={16} horizontal>
<Icon icon={Loader2} spin />
{t('appInitializing')}
</Center>
</Center>
</Flexbox>
);
return (
<Flexbox height={'100vh'} width={'100%'}>
<Center flex={1} gap={12} width={'100%'}>
<Logo extra={'SD'} size={48} type={'combine'} />
<Center gap={16} horizontal>
<Icon icon={Loader2} spin />
{t('custom.initializing')}
</Center>
</Center>
</Flexbox>
);
});
export default Loading;

View File

@ -2,13 +2,13 @@ import { createStyles } from 'antd-style';
import { adjustHue, rgba } from 'polished';
export const useStyles = createStyles(
(
{ css, stylish, cx, token },
{ isPrimaryColor, liteAnimation }: { isPrimaryColor?: boolean; liteAnimation?: boolean },
) => ({
canvas: cx(
stylish.gradientAnimation,
isPrimaryColor &&
(
{ css, stylish, cx, token },
{ isPrimaryColor, liteAnimation }: { isPrimaryColor?: boolean; liteAnimation?: boolean },
) => ({
canvas: cx(
stylish.gradientAnimation,
isPrimaryColor &&
css`
background-image: linear-gradient(
-45deg,
@ -18,7 +18,7 @@ export const useStyles = createStyles(
${adjustHue(-45, token.colorPrimary)}
);
`,
css`
css`
pointer-events: none;
position: absolute;
@ -33,10 +33,10 @@ export const useStyles = createStyles(
opacity: 0.2;
filter: blur(100px);
`,
),
container: cx(
!liteAnimation && stylish.blur,
css`
),
container: cx(
!liteAnimation && stylish.blur,
css`
position: fixed;
z-index: 9999;
inset: 0;
@ -50,11 +50,11 @@ export const useStyles = createStyles(
background: ${liteAnimation ? token.colorBgLayout : rgba(token.colorBgLayout, 0.5)};
`,
),
icon: css`
),
icon: css`
color: ${token.colorPrimary};
`,
inner: css`
inner: css`
display: flex;
flex-direction: column;
gap: 16px;
@ -63,5 +63,5 @@ export const useStyles = createStyles(
width: min(50%, 580px);
`,
}),
}),
);

View File

@ -10,25 +10,25 @@ export interface CustomLogoProps {
}
const CustomLogo = memo<CustomLogoProps>(({ size = 32, style, logoCustomUrl, logoCustomTitle }) => {
let customLogo = <LobeLogo size={size} style={style} />;
let customLogo = <LobeLogo size={size} style={style} />;
if (logoCustomUrl) {
if (logoCustomUrl.includes('http') || logoCustomUrl.includes('data')) {
customLogo = <img alt="logo" src={logoCustomUrl} style={{ height: size, ...style }} />;
} else {
const pureEmoji = getEmoji(logoCustomUrl);
if (pureEmoji) {
customLogo = <FluentEmoji emoji={pureEmoji} size={size} style={style} />;
}
if (logoCustomUrl) {
if (logoCustomUrl.includes('http') || logoCustomUrl.includes('data')) {
customLogo = <img alt="logo" src={logoCustomUrl} style={{ height: size, ...style }} />;
} else {
const pureEmoji = getEmoji(logoCustomUrl);
if (pureEmoji) {
customLogo = <FluentEmoji emoji={pureEmoji} size={size} style={style} />;
}
}
}
}
return (
<Space align="center" size={size * 0.3}>
{customLogo}
<b style={{ fontSize: size * 0.6, whiteSpace: 'nowrap' }}>{logoCustomTitle}</b>
</Space>
);
return (
<Space align="center" size={size * 0.3}>
{customLogo}
<b style={{ fontSize: size * 0.6, whiteSpace: 'nowrap' }}>{logoCustomTitle}</b>
</Space>
);
});
export default CustomLogo;

View File

@ -9,13 +9,13 @@ export interface KitchenLogoProps {
}
const KitchenLogo = memo<KitchenLogoProps>(({ size = 32, style, themeMode }) => {
return (
<img
alt="logo"
src={themeMode === 'dark' ? darkLogo : lightLogo}
style={{ height: size, ...style }}
/>
);
return (
<img
alt="logo"
src={themeMode === 'dark' ? darkLogo : lightLogo}
style={{ height: size, ...style }}
/>
);
});
export default KitchenLogo;

View File

@ -1,7 +1,6 @@
import { Logo as LobeLogo } from '@lobehub/ui';
import isEqual from 'fast-deep-equal';
import { type CSSProperties, memo } from 'react';
import { shallow } from 'zustand/shallow';
import { selectors, useAppStore } from '@/store';
@ -14,25 +13,25 @@ export interface LogoProps {
}
const Logo = memo<LogoProps>(({ size = 32, style }) => {
const setting = useAppStore(selectors.currentSetting, isEqual);
const themeMode = useAppStore(selectors.themeMode, shallow);
const setting = useAppStore(selectors.currentSetting, isEqual);
const themeMode = useAppStore(selectors.themeMode);
if (setting.logoType === 'kitchen') {
return <KitchenLogo size={size * 0.75} style={style} themeMode={themeMode} />;
}
if (setting.logoType === 'kitchen') {
return <KitchenLogo size={size * 0.75} style={style} themeMode={themeMode} />;
}
if (setting.logoType === 'custom') {
return (
<CustomLogo
logoCustomTitle={setting.logoCustomTitle}
logoCustomUrl={setting.logoCustomUrl}
size={size}
style={style}
/>
);
}
if (setting.logoType === 'custom') {
return (
<CustomLogo
logoCustomTitle={setting.logoCustomTitle}
logoCustomUrl={setting.logoCustomUrl}
size={size}
style={style}
/>
);
}
return <LobeLogo extra="SD" size={size} style={style} type="combine" />;
return <LobeLogo extra="SD" size={size} style={style} type="combine" />;
});
export default Logo;

View File

@ -1,3 +1,4 @@
import { consola } from 'consola';
import { memo, useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
@ -10,66 +11,66 @@ interface PromptProps {
}
const Prompt = memo<PromptProps>(({ type }) => {
const [tags, setTags] = useState<TagItem[]>([]);
const { styles } = useStyles();
const { t } = useTranslation();
const [tags, setTags] = useState<TagItem[]>([]);
const { styles } = useStyles();
const { t } = useTranslation();
const id =
const id =
type === 'positive' ? "[id$='2img_prompt'] textarea" : "[id$='2img_neg_prompt'] textarea";
const getValue = useCallback(() => {
try {
const textarea = get_uiCurrentTabContent().querySelector(id) as HTMLTextAreaElement;
if (textarea) setTags(formatPrompt(textarea.value));
} catch (error) {
console.debug(error);
}
}, []);
const getValue = useCallback(() => {
try {
const textarea = get_uiCurrentTabContent().querySelector(id) as HTMLTextAreaElement;
if (textarea) setTags(formatPrompt(textarea.value));
} catch (error) {
consola.error('🤯 [prompt]', error);
}
}, []);
const setValue = useCallback(() => {
try {
const newValue = tags.map((t) => t.text).join(', ');
const textarea = get_uiCurrentTabContent().querySelector(id) as HTMLTextAreaElement;
if (textarea) textarea.value = newValue;
updateInput(textarea);
} catch (error) {
console.debug(error);
}
}, [tags, type]);
const setValue = useCallback(() => {
try {
const newValue = tags.map((t) => t.text).join(', ');
const textarea = get_uiCurrentTabContent().querySelector(id) as HTMLTextAreaElement;
if (textarea) textarea.value = newValue;
updateInput(textarea);
} catch (error) {
consola.error('🤯 [prompt]', error);
}
}, [tags, type]);
const setCurrentValue = useCallback((currentTags: TagItem[]) => {
try {
const textarea = get_uiCurrentTabContent().querySelector(id) as HTMLTextAreaElement;
if (textarea) textarea.value = currentTags.map((t) => t.text).join(', ');
updateInput(textarea);
} catch (error) {
console.debug(error);
}
}, []);
const setCurrentValue = useCallback((currentTags: TagItem[]) => {
try {
const textarea = get_uiCurrentTabContent().querySelector(id) as HTMLTextAreaElement;
if (textarea) textarea.value = currentTags.map((t) => t.text).join(', ');
updateInput(textarea);
} catch (error) {
consola.error('🤯 [prompt]', error);
}
}, []);
return (
<div className={styles.promptView}>
<TagList setTags={setTags} setValue={setCurrentValue} tags={tags} type={type} />
<div className={styles.buttonGroup}>
<button
className="lg secondary gradio-button tool svelte-1ipelgc"
onClick={getValue}
title={t('loadPrompt')}
type="button"
>
return (
<div className={styles.promptView}>
<TagList setTags={setTags} setValue={setCurrentValue} tags={tags} type={type} />
<div className={styles.buttonGroup}>
<button
className="lg secondary gradio-button tool svelte-1ipelgc"
onClick={getValue}
title={t('prompt.load')}
type="button"
>
🔄
</button>
<button
className="lg secondary gradio-button tool svelte-1ipelgc"
onClick={setValue}
title={t('setPrompt')}
type="button"
>
</button>
<button
className="lg secondary gradio-button tool svelte-1ipelgc"
onClick={setValue}
title={t('prompt.set')}
type="button"
>
</button>
</div>
</div>
);
</button>
</div>
</div>
);
});
export default Prompt;

View File

@ -1,3 +1,4 @@
import { consola } from 'consola';
import { type FC, memo, useCallback, useEffect, useState } from 'react';
import { WithContext, ReactTagsProps as WithContextProps } from 'react-tag-input';
@ -21,8 +22,8 @@ interface ReactTagsProps extends WithContextProps {
const ReactTags: FC<ReactTagsProps> = WithContext;
const KeyCodes = {
comma: 188,
enter: 13,
comma: 188,
enter: 13,
};
const delimiters = [KeyCodes.comma, KeyCodes.enter];
@ -35,86 +36,85 @@ interface TagListProps {
}
const TagList = memo<TagListProps>(({ tags, setTags, type, setValue }) => {
const id = `${type}_tag_editor`;
const [bind, setBind] = useState(false);
const { styles } = useStyles(type);
const handleDelete = useCallback(
(index_: number) => {
const newTags = tags.filter((tag, index) => index !== index_);
setTags(newTags);
setValue(newTags);
},
[tags],
);
const id = `${type}_tag_editor`;
const [bind, setBind] = useState(false);
const { styles } = useStyles(type);
const handleDelete = useCallback(
(index_: number) => {
const newTags = tags.filter((tag, index) => index !== index_);
setTags(newTags);
setValue(newTags);
},
[tags],
);
const handleAddition = useCallback(
(tag: TagItem) => {
const newTags = [...tags, genTagType(tag)];
setTags(newTags);
setValue(newTags);
},
[tags],
);
const handleAddition = useCallback(
(tag: TagItem) => {
const newTags = [...tags, genTagType(tag)];
setTags(newTags);
setValue(newTags);
},
[tags],
);
const handleDrag = useCallback(
(tag: TagItem, currentPos: number, newPos: number) => {
const newTags = [...tags];
newTags.splice(currentPos, 1);
newTags.splice(newPos, 0, genTagType(tag));
setTags(newTags);
setValue(newTags);
},
[tags],
);
const handleDrag = useCallback(
(tag: TagItem, currentPos: number, newPos: number) => {
const newTags = [...tags];
newTags.splice(currentPos, 1);
newTags.splice(newPos, 0, genTagType(tag));
setTags(newTags);
setValue(newTags);
},
[tags],
);
const handleTagUpdate = useCallback(
(index: number, tag: TagItem) => {
const newTags = [...tags];
newTags[index] = genTagType(tag);
setTags(newTags);
setValue(newTags);
},
[tags],
);
const handleTagUpdate = useCallback(
(index: number, tag: TagItem) => {
const newTags = [...tags];
newTags[index] = genTagType(tag);
setTags(newTags);
setValue(newTags);
},
[tags],
);
useEffect(() => {
try {
if (!addAutocompleteToArea || bind) return;
let retryTimes = 0;
const bindInterval = setInterval(() => {
console.time('🤯 [promptTagEditor] inject');
if (bind || retryTimes > 10) {
const inputDom = document.querySelector(`#${id}`) as HTMLInputElement;
if (inputDom) {
setBind(true);
addAutocompleteToArea(inputDom);
clearInterval(bindInterval);
console.timeEnd('🤯 [promptTagEditor] inject');
}
useEffect(() => {
try {
if (!addAutocompleteToArea || bind) return;
let retryTimes = 0;
const bindInterval = setInterval(() => {
if (bind || retryTimes > 10) {
const inputDom = document.querySelector(`#${id}`) as HTMLInputElement;
if (inputDom) {
setBind(true);
addAutocompleteToArea(inputDom);
clearInterval(bindInterval);
consola.success('🤯 [promptTagEditor] inject');
}
}
retryTimes++;
}, 1000);
} catch (error) {
consola.error('🤯 [promptTagEditor]', error);
}
retryTimes++;
}, 1000);
} catch (error) {
console.error(error);
}
}, [bind]);
}, [bind]);
return (
<div className={styles}>
<ReactTags
delimiters={delimiters}
editable
handleAddition={handleAddition}
handleDelete={handleDelete}
handleDrag={handleDrag}
id={id}
inline
inputFieldPosition="bottom"
onTagUpdate={handleTagUpdate}
tags={tags}
/>
</div>
);
return (
<div className={styles}>
<ReactTags
delimiters={delimiters}
editable
handleAddition={handleAddition}
handleDelete={handleDelete}
handleDrag={handleDrag}
id={id}
inline
inputFieldPosition="bottom"
onTagUpdate={handleTagUpdate}
tags={tags}
/>
</div>
);
});
export default TagList;

View File

@ -1,7 +1,7 @@
import { createStyles } from 'antd-style';
export const useStyles = createStyles(
({ css, token }, type: 'positive' | 'negative') => css`
({ css, token }, type: 'positive' | 'negative') => css`
.autocompleteResults {
left: 16px !important;
min-width: 200px !important;

View File

@ -6,16 +6,16 @@ import { useStyles } from '@/components/PromptEditor/style';
import Prompt from './Prompt';
const PromptEditor = memo(() => {
const { styles } = useStyles();
const { t } = useTranslation();
return (
<div className={styles.view}>
<span style={{ marginBottom: -10 }}>{t('positive')}</span>
<Prompt type="positive" />
<span style={{ marginBottom: -10 }}>{t('negative')}</span>
<Prompt type="negative" />
</div>
);
const { styles } = useStyles();
const { t } = useTranslation();
return (
<div className={styles.view}>
<span style={{ marginBottom: -10 }}>{t('prompt.positive')}</span>
<Prompt type="positive" />
<span style={{ marginBottom: -10 }}>{t('prompt.negative')}</span>
<Prompt type="negative" />
</div>
);
});
export default PromptEditor;

View File

@ -1,16 +1,16 @@
import { createStyles } from 'antd-style';
export const useStyles = createStyles(({ css }) => ({
buttonGroup: css`
buttonGroup: css`
display: flex;
gap: 8px;
`,
promptView: css`
promptView: css`
display: flex;
flex-direction: column;
gap: 8px;
`,
view: css`
view: css`
display: flex;
flex-direction: column;
gap: 1em;

View File

@ -3,31 +3,31 @@ import { Converter } from '@/scripts/formatPrompt';
import { TagItem } from './TagList';
export const genTagType = (tag: TagItem): TagItem => {
const newTag = tag;
if (newTag.text.includes('<lora')) {
newTag.className = 'ReactTags__lora';
} else if (newTag.text.includes('<hypernet')) {
newTag.className = 'ReactTags__hypernet';
} else if (newTag.text.includes('<embedding')) {
newTag.className = 'ReactTags__embedding';
} else {
newTag.className = undefined;
}
return newTag;
const newTag = tag;
if (newTag.text.includes('<lora')) {
newTag.className = 'ReactTags__lora';
} else if (newTag.text.includes('<hypernet')) {
newTag.className = 'ReactTags__hypernet';
} else if (newTag.text.includes('<embedding')) {
newTag.className = 'ReactTags__embedding';
} else {
newTag.className = undefined;
}
return newTag;
};
export const formatPrompt = (value: string) => {
const text = Converter.convertStr(value);
const textArray = Converter.convertStr2Array(text).map((item) => {
if (item.includes('<')) return item;
const newItem = item
.replaceAll(/\s+/g, ' ')
.replaceAll(/|\.\|。/g, ',')
.replaceAll(/“||”|"|\/'/g, '')
.replaceAll(', ', ',')
.replaceAll(',,', ',')
.replaceAll(',', ', ');
return Converter.convertStr2Array(newItem).join(', ');
});
return textArray.map((tag) => genTagType({ id: tag, text: tag }));
const text = Converter.convertStr(value);
const textArray = Converter.convertStr2Array(text).map((item) => {
if (item.includes('<')) return item;
const newItem = item
.replaceAll(/\s+/g, ' ')
.replaceAll(/|\.\|。/g, ',')
.replaceAll(/“||”|"|\/'/g, '')
.replaceAll(', ', ',')
.replaceAll(',,', ',')
.replaceAll(',', ', ');
return Converter.convertStr2Array(newItem).join(', ');
});
return textArray.map((tag) => genTagType({ id: tag, text: tag }));
};

View File

@ -7,26 +7,26 @@ import { homepage } from '@/../package.json';
import { useAppStore } from '@/store';
const VersionTag = memo<TagProps>((props) => {
const { version, latestVersion } = useAppStore(
(st) => ({ latestVersion: st.latestVersion, version: st.version }),
shallow,
);
const { version, latestVersion } = useAppStore(
(st) => ({ latestVersion: st.latestVersion, version: st.version }),
shallow,
);
const isLatest = semver.gte(version, latestVersion);
const isLatest = semver.gte(version, latestVersion);
return (
<a href={homepage} rel="noreferrer" target="_blank">
{isLatest ? (
<Tag color="success" {...props}>
return (
<a href={homepage} rel="noreferrer" target="_blank">
{isLatest ? (
<Tag color="success" {...props}>
v{version}
</Tag>
) : (
<Tag color="warning" {...props}>
</Tag>
) : (
<Tag color="warning" {...props}>
v{version} / latest v{latestVersion}
</Tag>
)}
</a>
);
</Tag>
)}
</a>
);
});
export default VersionTag;

View File

@ -1,43 +1,47 @@
import { consola } from 'consola';
import { memo, useEffect } from 'react';
const Preview = memo(() => {
useEffect(() => {
console.time('🤯 [layout] inject - Split Previewer');
// tab_txt2img
const txt2imgToprow = gradioApp().querySelector('#txt2img_toprow') as HTMLDivElement;
const txt2imgSettings = gradioApp().querySelector('#txt2img_settings') as HTMLDivElement;
const txt2imgGenerate = gradioApp().querySelector('#txt2img_generate_box') as HTMLDivElement;
const txt2imgPreview = gradioApp().querySelector(
'#txt2img_gallery_container',
) as HTMLDivElement;
if (txt2imgToprow && txt2imgSettings && txt2imgGenerate && txt2imgPreview) {
txt2imgSettings.prepend(txt2imgToprow);
txt2imgPreview.prepend(txt2imgGenerate);
}
// tab_img2img
const img2imgToprow = gradioApp().querySelector('#img2img_toprow') as HTMLDivElement;
const img2imgSettings = gradioApp().querySelector('#img2img_settings') as HTMLDivElement;
const img2imgGenerate = gradioApp().querySelector('#img2img_generate_box') as HTMLDivElement;
const img2imgPreview = gradioApp().querySelector(
'#img2img_gallery_container',
) as HTMLDivElement;
if (img2imgSettings && img2imgToprow && img2imgGenerate && img2imgPreview) {
img2imgSettings.prepend(img2imgToprow);
img2imgPreview.prepend(img2imgGenerate);
}
useEffect(() => {
try {
// tab_txt2img
const txt2imgToprow = gradioApp().querySelector('#txt2img_toprow') as HTMLDivElement;
const txt2imgSettings = gradioApp().querySelector('#txt2img_settings') as HTMLDivElement;
const txt2imgGenerate = gradioApp().querySelector('#txt2img_generate_box') as HTMLDivElement;
const txt2imgPreview = gradioApp().querySelector(
'#txt2img_gallery_container',
) as HTMLDivElement;
if (txt2imgToprow && txt2imgSettings && txt2imgGenerate && txt2imgPreview) {
txt2imgSettings.prepend(txt2imgToprow);
txt2imgPreview.prepend(txt2imgGenerate);
}
// tab_img2img
const img2imgToprow = gradioApp().querySelector('#img2img_toprow') as HTMLDivElement;
const img2imgSettings = gradioApp().querySelector('#img2img_settings') as HTMLDivElement;
const img2imgGenerate = gradioApp().querySelector('#img2img_generate_box') as HTMLDivElement;
const img2imgPreview = gradioApp().querySelector(
'#img2img_gallery_container',
) as HTMLDivElement;
if (img2imgSettings && img2imgToprow && img2imgGenerate && img2imgPreview) {
img2imgSettings.prepend(img2imgToprow);
img2imgPreview.prepend(img2imgGenerate);
}
// extras_img2img
const extrasGenerate = gradioApp().querySelector('#extras_generate') as HTMLDivElement;
const extrasPreview = gradioApp().querySelector('#extras_results') as HTMLDivElement;
if (extrasGenerate && extrasPreview) {
(extrasPreview?.parentNode as HTMLDivElement).id = '#extras_gallery_container';
extrasPreview.prepend(extrasGenerate);
}
// extras_img2img
const extrasGenerate = gradioApp().querySelector('#extras_generate') as HTMLDivElement;
const extrasPreview = gradioApp().querySelector('#extras_results') as HTMLDivElement;
if (extrasGenerate && extrasPreview) {
(extrasPreview?.parentNode as HTMLDivElement).id = '#extras_gallery_container';
extrasPreview.prepend(extrasGenerate);
}
console.timeEnd('🤯 [layout] inject - Split Previewer');
}, []);
consola.success('🤯 [layout] inject - Split Previewer');
} catch (error) {
consola.error('🤯 [layout] inject - Split Previewer', error);
}
}, []);
return null;
return null;
});
export default Preview;

View File

@ -1,4 +1,5 @@
import { useResponsive } from 'antd-style';
import { consola } from 'consola';
import isEqual from 'fast-deep-equal';
import { memo, useEffect, useRef } from 'react';
@ -10,63 +11,66 @@ import SplitView from './SplitView';
import { useStyles } from './style';
const Content = memo<DivProps>(({ className, ...props }) => {
const mainReference = useRef<HTMLDivElement>(null);
const { mobile } = useResponsive();
const setting = useAppStore(selectors.currentSetting, isEqual);
const mainReference = useRef<HTMLDivElement>(null);
const { mobile } = useResponsive();
const setting = useAppStore(selectors.currentSetting, isEqual);
const { cx, styles } = useStyles({
isPromptResizable: setting.promptTextareaType === 'resizable',
layoutSplitPreview: setting.layoutSplitPreview,
});
const { cx, styles } = useStyles({
isPromptResizable: setting.promptTextareaType === 'resizable',
layoutSplitPreview: setting.layoutSplitPreview,
});
useEffect(() => {
console.time('🤯 [layout] inject - Content');
// Content
const main = gradioApp().querySelector('.app');
if (main) {
mainReference.current?.append(main);
}
useEffect(() => {
try {
// Content
const main = gradioApp().querySelector('.app');
if (main) {
mainReference.current?.append(main);
}
// remove prompt scroll-hide
const textares = gradioApp().querySelectorAll(
`[id$="_prompt_container"] textarea`,
) as NodeListOf<HTMLTextAreaElement>;
if (textares) {
for (const textarea of textares) {
textarea.classList.remove('scroll-hide');
textarea.style.height = 'auto';
}
}
// remove prompt scroll-hide
const textares = gradioApp().querySelectorAll(
`[id$="_prompt_container"] textarea`,
) as NodeListOf<HTMLTextAreaElement>;
if (textares) {
for (const textarea of textares) {
textarea.classList.remove('scroll-hide');
textarea.style.height = 'auto';
}
}
// textarea
const interrogate = gradioApp().querySelector(
'#img2img_toprow .interrogate-col',
) as HTMLDivElement;
const actions = gradioApp().querySelector('#img2img_actions_column') as HTMLDivElement;
if (interrogate && actions) {
actions.append(interrogate);
}
// textarea
const interrogate = gradioApp().querySelector(
'#img2img_toprow .interrogate-col',
) as HTMLDivElement;
const actions = gradioApp().querySelector('#img2img_actions_column') as HTMLDivElement;
if (interrogate && actions) {
actions.append(interrogate);
}
formatPrompt();
console.timeEnd('🤯 [layout] inject - Content');
}, []);
formatPrompt();
consola.success('🤯 [layout] inject - Content');
} catch (error) {
consola.error('🤯 [layout] inject - Content', error);
}
}, []);
return (
<>
<div
className={cx(
styles.container,
styles.textares,
styles.text2img,
setting.layoutSplitPreview && styles.splitView,
className,
)}
ref={mainReference}
{...props}
/>
{setting.layoutSplitPreview && mobile === false && <SplitView />}
</>
);
return (
<>
<div
className={cx(
styles.container,
styles.textares,
styles.text2img,
setting.layoutSplitPreview && styles.splitView,
className,
)}
ref={mainReference}
{...props}
/>
{setting.layoutSplitPreview && mobile === false && <SplitView />}
</>
);
});
export default Content;

View File

@ -3,15 +3,15 @@ import { createStyles } from 'antd-style';
const TEXT2IMG_PROMPT_HEIGHT = 74;
const IMG2IMG_PROMPT_HEIGHT = 98;
export const useStyles = createStyles(
(
{ css, token, stylish, isDarkMode, responsive },
{
isPromptResizable,
layoutSplitPreview,
}: { isPromptResizable: boolean; layoutSplitPreview: boolean },
) => {
return {
container: css`
(
{ css, token, stylish, isDarkMode, responsive },
{
isPromptResizable,
layoutSplitPreview,
}: { isPromptResizable: boolean; layoutSplitPreview: boolean },
) => {
return {
container: css`
position: relative;
flex: 1;
min-width: ${layoutSplitPreview ? '200px' : '0'};
@ -191,7 +191,7 @@ export const useStyles = createStyles(
}
}
`,
splitView: css`
splitView: css`
#txt2img_toprow,
#img2img_toprow {
flex-direction: column !important;
@ -199,7 +199,7 @@ export const useStyles = createStyles(
background: transparent !important;
}
`,
text2img: css`
text2img: css`
button[id$='_generate'] {
height: var(--button-lg-height) !important;
min-height: var(--button-lg-height) !important;
@ -364,7 +364,7 @@ export const useStyles = createStyles(
box-shadow: none;
}
`,
textares: css`
textares: css`
[id$='2img_prompt'],
[id$='2img_neg_prompt'] {
textarea {
@ -452,6 +452,6 @@ export const useStyles = createStyles(
}
}
`,
};
},
};
},
);

View File

@ -1,136 +1,141 @@
import { ActionIcon, DraggablePanelBody, DraggablePanelFooter } from '@lobehub/ui';
import { useTimeout } from 'ahooks';
import { Skeleton, Slider } from 'antd';
import { consola } from 'consola';
import isEqual from 'fast-deep-equal';
import { ZoomIn, ZoomOut } from 'lucide-react';
import { memo, useEffect, useRef, useState } from 'react';
import { shallow } from 'zustand/shallow';
import { useStyles } from '@/features/ExtraNetworkSidebar/style';
import civitaiHelperFix from '@/scripts/civitaiHelperFix';
import { selectors, useAppStore } from '@/store';
const Inner = memo(() => {
const txt2imgExtraNetworkSidebarReference = useRef<HTMLDivElement>(null);
const img2imgExtraNetworkSidebarReference = useRef<HTMLDivElement>(null);
const [extraLoading, setExtraLoading] = useState(true);
const setting = useAppStore(selectors.currentSetting, isEqual);
const currentTab = useAppStore(selectors.currentTab, shallow);
const [size, setSize] = useState<number>(setting.extraNetworkCardSize || 86);
const { styles } = useStyles({ size });
const txt2imgExtraNetworkSidebarReference = useRef<HTMLDivElement>(null);
const img2imgExtraNetworkSidebarReference = useRef<HTMLDivElement>(null);
const [extraLoading, setExtraLoading] = useState(true);
const setting = useAppStore(selectors.currentSetting, isEqual);
const currentTab = useAppStore(selectors.currentTab);
const [size, setSize] = useState<number>(setting.extraNetworkCardSize || 86);
const { styles } = useStyles({ size });
useEffect(() => {
console.time('🤯 [layout] inject - ExtraNetworkSidebar');
if (setting.enableExtraNetworkSidebar) {
const image2imageExtraNetworkButton = gradioApp().querySelectorAll(
'#txt2img_extra_tabs > .tab-nav > button',
)[1] as HTMLButtonElement;
const text2imageExtraNetworkButton = gradioApp().querySelectorAll(
'#img2img_extra_tabs > .tab-nav > button',
)[1] as HTMLButtonElement;
useEffect(() => {
try {
if (setting.enableExtraNetworkSidebar) {
const image2imageExtraNetworkButton = gradioApp().querySelectorAll(
'#txt2img_extra_tabs > .tab-nav > button',
)[1] as HTMLButtonElement;
const text2imageExtraNetworkButton = gradioApp().querySelectorAll(
'#img2img_extra_tabs > .tab-nav > button',
)[1] as HTMLButtonElement;
if (image2imageExtraNetworkButton) {
image2imageExtraNetworkButton.click();
}
if (text2imageExtraNetworkButton) {
text2imageExtraNetworkButton.click();
}
if (image2imageExtraNetworkButton) {
image2imageExtraNetworkButton.click();
}
if (text2imageExtraNetworkButton) {
text2imageExtraNetworkButton.click();
}
const txt2imgTab = gradioApp().querySelector('div#tab_txt2img') as HTMLDivElement;
const txt2imgExtraNetworks = gradioApp().querySelector(
'div#txt2img_extra_tabs',
) as HTMLDivElement;
const txt2imgRender = txt2imgExtraNetworks.querySelectorAll(
'div.tabitem.gradio-tabitem',
)[0] as HTMLDivElement;
const txt2imgTab = gradioApp().querySelector('div#tab_txt2img') as HTMLDivElement;
const txt2imgExtraNetworks = gradioApp().querySelector(
'div#txt2img_extra_tabs',
) as HTMLDivElement;
const txt2imgRender = txt2imgExtraNetworks.querySelectorAll(
'div.tabitem.gradio-tabitem',
)[0] as HTMLDivElement;
const img2imgTab = gradioApp().querySelector('div#tab_img2img');
const img2imgExtraNetworks = gradioApp().querySelector(
'div#img2img_extra_tabs',
) as HTMLDivElement;
const img2imgRender = img2imgExtraNetworks.querySelectorAll(
'div.tabitem.gradio-tabitem',
)[0] as HTMLDivElement;
const img2imgTab = gradioApp().querySelector('div#tab_img2img');
const img2imgExtraNetworks = gradioApp().querySelector(
'div#img2img_extra_tabs',
) as HTMLDivElement;
const img2imgRender = img2imgExtraNetworks.querySelectorAll(
'div.tabitem.gradio-tabitem',
)[0] as HTMLDivElement;
if (txt2imgExtraNetworks && img2imgExtraNetworks) {
txt2imgExtraNetworkSidebarReference.current?.append(txt2imgExtraNetworks);
txt2imgRender.id = 'txt2img_render';
txt2imgTab?.append(txt2imgRender);
if (txt2imgExtraNetworks && img2imgExtraNetworks) {
txt2imgExtraNetworkSidebarReference.current?.append(txt2imgExtraNetworks);
txt2imgRender.id = 'txt2img_render';
txt2imgTab?.append(txt2imgRender);
img2imgExtraNetworkSidebarReference.current?.append(img2imgExtraNetworks);
img2imgRender.id = 'img2img_render';
img2imgTab?.append(img2imgRender);
}
if (document.querySelector('.extra-network-cards')) {
civitaiHelperFix();
setExtraLoading(false);
return;
}
}
console.timeEnd('🤯 [layout] inject - ExtraNetworkSidebar');
}, []);
img2imgExtraNetworkSidebarReference.current?.append(img2imgExtraNetworks);
img2imgRender.id = 'img2img_render';
img2imgTab?.append(img2imgRender);
}
if (document.querySelector('.extra-network-cards')) {
civitaiHelperFix();
setExtraLoading(false);
return;
}
}
consola.success('🤯 [layout] inject - ExtraNetworkSidebar');
} catch (error) {
consola.error('🤯 [layout] inject - ExtraNetworkSidebar', error);
}
}, []);
useTimeout(() => {
console.time('🤯 [extranetwork] force reload');
const t2indexButton = document.querySelector('#txt2img_extra_refresh') as HTMLButtonElement;
const index2indexButton = document.querySelector('#img2img_extra_refresh') as HTMLButtonElement;
t2indexButton.click();
index2indexButton.click();
setExtraLoading(false);
try {
const civitaiText2ImgButton = document.querySelector('#txt2img_extra_refresh')
?.nextSibling as HTMLButtonElement;
if (civitaiText2ImgButton) {
civitaiText2ImgButton.onclick = civitaiHelperFix;
}
const civitaiImg2ImgButton = document.querySelector('#img2img_extra_refresh')
?.nextSibling as HTMLButtonElement;
if (civitaiImg2ImgButton) {
civitaiImg2ImgButton.onclick = civitaiHelperFix;
}
useTimeout(() => {
try {
const t2indexButton = document.querySelector('#txt2img_extra_refresh') as HTMLButtonElement;
const index2indexButton = document.querySelector(
'#img2img_extra_refresh',
) as HTMLButtonElement;
t2indexButton.click();
index2indexButton.click();
setExtraLoading(false);
civitaiHelperFix();
} catch (error) {
console.debug(error);
}
console.timeEnd('🤯 [extranetwork] force reload');
}, 2000);
const civitaiText2ImgButton = document.querySelector('#txt2img_extra_refresh')
?.nextSibling as HTMLButtonElement;
if (civitaiText2ImgButton) {
civitaiText2ImgButton.onclick = civitaiHelperFix;
}
const civitaiImg2ImgButton = document.querySelector('#img2img_extra_refresh')
?.nextSibling as HTMLButtonElement;
if (civitaiImg2ImgButton) {
civitaiImg2ImgButton.onclick = civitaiHelperFix;
}
return (
<>
<DraggablePanelBody className={styles.body}>
{extraLoading && <Skeleton active />}
<div style={extraLoading ? { display: 'none' } : {}}>
<div
id="txt2img-extra-network-sidebar"
ref={txt2imgExtraNetworkSidebarReference}
style={currentTab === 'tab_img2img' ? { display: 'none' } : {}}
/>
<div
id="img2img-extra-network-sidebar"
ref={img2imgExtraNetworkSidebarReference}
style={currentTab === 'tab_img2img' ? {} : { display: 'none' }}
/>
</div>
</DraggablePanelBody>
<DraggablePanelFooter>
<ActionIcon
icon={setting.extraNetworkCardSize < size ? ZoomOut : ZoomIn}
onClick={() => setSize(setting.extraNetworkCardSize)}
size={{ blockSize: 24, fontSize: 16 }}
/>
<Slider
defaultValue={size}
max={256}
min={64}
onChange={setSize}
step={8}
style={{ flex: 1 }}
value={size}
/>
</DraggablePanelFooter>
</>
);
civitaiHelperFix();
consola.success('🤯 [extranetwork] force reload');
} catch (error) {
consola.error('🤯 [extranetwork] force reload', error);
}
}, 2000);
return (
<>
<DraggablePanelBody className={styles.body}>
{extraLoading && <Skeleton active />}
<div style={extraLoading ? { display: 'none' } : {}}>
<div
id="txt2img-extra-network-sidebar"
ref={txt2imgExtraNetworkSidebarReference}
style={currentTab === 'tab_img2img' ? { display: 'none' } : {}}
/>
<div
id="img2img-extra-network-sidebar"
ref={img2imgExtraNetworkSidebarReference}
style={currentTab === 'tab_img2img' ? {} : { display: 'none' }}
/>
</div>
</DraggablePanelBody>
<DraggablePanelFooter>
<ActionIcon
icon={setting.extraNetworkCardSize < size ? ZoomOut : ZoomIn}
onClick={() => setSize(setting.extraNetworkCardSize)}
size={{ blockSize: 24, fontSize: 16 }}
/>
<Slider
defaultValue={size}
max={256}
min={64}
onChange={setSize}
step={8}
style={{ flex: 1 }}
value={size}
/>
</DraggablePanelFooter>
</>
);
});
export default Inner;

View File

@ -1,8 +1,8 @@
import {
DraggablePanel,
DraggablePanelContainer,
DraggablePanelHeader,
LayoutSidebarInner,
DraggablePanel,
DraggablePanelContainer,
DraggablePanelHeader,
LayoutSidebarInner,
} from '@lobehub/ui';
import { useResponsive } from 'antd-style';
import isEqual from 'fast-deep-equal';
@ -20,50 +20,50 @@ export interface ExtraNetworkSidebarProps extends DivProps {
}
const ExtraNetworkSidebar = memo<ExtraNetworkSidebarProps>(({ headerHeight }) => {
const { mobile } = useResponsive();
const setting = useAppStore(selectors.currentSetting, isEqual);
const [expand, setExpand] = useState<boolean>(mobile ? false : setting.extraNetworkSidebarExpand);
const [pin, setPin] = useState<boolean>(setting.extraNetworkFixedMode === 'fixed');
const { styles, theme } = useStyles({ headerHeight });
const { t } = useTranslation();
const { mobile } = useResponsive();
const setting = useAppStore(selectors.currentSetting, isEqual);
const [expand, setExpand] = useState<boolean>(mobile ? false : setting.extraNetworkSidebarExpand);
const [pin, setPin] = useState<boolean>(setting.extraNetworkFixedMode === 'fixed');
const { styles, theme } = useStyles({ headerHeight });
const { t } = useTranslation();
useEffect(() => {
if (mobile) setExpand(false);
}, [mobile]);
useEffect(() => {
if (mobile) setExpand(false);
}, [mobile]);
const mode = mobile ? 'fixed' : pin ? 'fixed' : 'float';
const mode = mobile ? 'fixed' : pin ? 'fixed' : 'float';
return (
<DraggablePanel
defaultSize={{ width: setting.extraNetworkSidebarWidth }}
expand={expand}
minWidth={setting.extraNetworkSidebarWidth}
mode={mode}
onExpandChange={setExpand}
pin={pin}
placement="right"
>
<LayoutSidebarInner>
<DraggablePanelContainer
className={styles.container}
style={
mode === 'float' ?
{ background: theme.colorBgContainer, minWidth: setting.extraNetworkSidebarWidth } :
{ minWidth: setting.extraNetworkSidebarWidth }
}
>
<DraggablePanelHeader
return (
<DraggablePanel
defaultSize={{ width: setting.extraNetworkSidebarWidth }}
expand={expand}
minWidth={setting.extraNetworkSidebarWidth}
mode={mode}
onExpandChange={setExpand}
pin={pin}
position="right"
setExpand={setExpand}
setPin={setPin}
title={t('extraNetwork')}
/>
<Inner />
</DraggablePanelContainer>
</LayoutSidebarInner>
</DraggablePanel>
);
placement="right"
>
<LayoutSidebarInner>
<DraggablePanelContainer
className={styles.container}
style={
mode === 'float' ?
{ background: theme.colorBgContainer, minWidth: setting.extraNetworkSidebarWidth } :
{ minWidth: setting.extraNetworkSidebarWidth }
}
>
<DraggablePanelHeader
pin={pin}
position="right"
setExpand={setExpand}
setPin={setPin}
title={t('sidebar.extraNetwork')}
/>
<Inner />
</DraggablePanelContainer>
</LayoutSidebarInner>
</DraggablePanel>
);
});
export default ExtraNetworkSidebar;

View File

@ -1,8 +1,8 @@
import { createStyles } from 'antd-style';
export const useStyles = createStyles(
({ css, token }, { headerHeight = 64, size = 86 }: { headerHeight?: number; size?: number }) => ({
body: css`
({ css, token }, { headerHeight = 64, size = 86 }: { headerHeight?: number; size?: number }) => ({
body: css`
.hide {
display: none;
}
@ -222,8 +222,8 @@ export const useStyles = createStyles(
font-family: ${token.fontFamily};
}
`,
container: css`
container: css`
height: calc(100vh - ${headerHeight}px);
`,
}),
}),
);

View File

@ -4,91 +4,91 @@ import { Bug, FileClock, GitFork, Github } from 'lucide-react';
import { homepage } from '../../../package.json';
export const Resources = [
{
description: 'AUTOMATIC111',
openExternal: true,
title: 'Stable Diffusion Webui',
url: 'https://github.com/AUTOMATIC1111/stable-diffusion-webui',
},
{
description: 'WebUI extension',
openExternal: true,
title: 'Controlnet',
url: 'https://github.com/Mikubill/sd-webui-controlnet',
},
{
description: 'Art models',
openExternal: true,
title: 'Civitai',
url: 'https://civitai.com/',
},
{
description: 'Artist Inspired Styles',
openExternal: true,
title: 'Cheat Sheet',
url: 'https://supagruen.github.io/StableDiffusion-CheatSheet',
},
{
description: 'Image Resizing',
openExternal: true,
title: 'Birme',
url: 'https://www.birme.net/?target_width=512&target_height=512',
},
{
description: 'AUTOMATIC111',
openExternal: true,
title: 'Stable Diffusion Webui',
url: 'https://github.com/AUTOMATIC1111/stable-diffusion-webui',
},
{
description: 'WebUI extension',
openExternal: true,
title: 'Controlnet',
url: 'https://github.com/Mikubill/sd-webui-controlnet',
},
{
description: 'Art models',
openExternal: true,
title: 'Civitai',
url: 'https://civitai.com/',
},
{
description: 'Artist Inspired Styles',
openExternal: true,
title: 'Cheat Sheet',
url: 'https://supagruen.github.io/StableDiffusion-CheatSheet',
},
{
description: 'Image Resizing',
openExternal: true,
title: 'Birme',
url: 'https://www.birme.net/?target_width=512&target_height=512',
},
];
export const Community = [
{
icon: <Icon icon={Bug} size="small" />,
openExternal: true,
title: 'Report Bug',
url: `${homepage}/issues/new/choose`,
},
{
icon: <Icon icon={GitFork} size="small" />,
openExternal: true,
title: 'Request Feature',
url: `${homepage}/issues/new/choose`,
},
{
icon: <Icon icon={Bug} size="small" />,
openExternal: true,
title: 'Report Bug',
url: `${homepage}/issues/new/choose`,
},
{
icon: <Icon icon={GitFork} size="small" />,
openExternal: true,
title: 'Request Feature',
url: `${homepage}/issues/new/choose`,
},
];
export const Help = [
{
icon: <Icon icon={Github} size="small" />,
openExternal: true,
title: 'GitHub',
url: homepage,
},
{
icon: <Icon icon={FileClock} size="small" />,
openExternal: true,
title: 'Changelog',
url: `${homepage}/blob/main/CHANGELOG.md`,
},
{
icon: <Icon icon={Github} size="small" />,
openExternal: true,
title: 'GitHub',
url: homepage,
},
{
icon: <Icon icon={FileClock} size="small" />,
openExternal: true,
title: 'Changelog',
url: `${homepage}/blob/main/CHANGELOG.md`,
},
];
export const MoreProducts = [
{
description: 'Minifier ExtraNetwrok Covers',
openExternal: true,
title: '✂️ Cover Minifier',
url: 'https://github.com/canisminor1990/sd-webui-cover-minifier',
},
{
description: 'OpenAI Chat Bot',
openExternal: true,
title: '🤖 Lobe Chat',
url: 'https://chat.lobehub.com',
},
{
description: 'AIGC Components',
openExternal: true,
title: '🍭 Lobe UI',
url: 'https://ui.lobehub.com',
},
{
description: 'AI Commit CLI',
openExternal: true,
title: '💌 Lobe Commit',
url: 'https://github.com/lobehub/lobe-commit',
},
{
description: 'Minifier ExtraNetwrok Covers',
openExternal: true,
title: '✂️ Cover Minifier',
url: 'https://github.com/canisminor1990/sd-webui-cover-minifier',
},
{
description: 'OpenAI Chat Bot',
openExternal: true,
title: '🤖 Lobe Chat',
url: 'https://chat.lobehub.com',
},
{
description: 'AIGC Components',
openExternal: true,
title: '🍭 Lobe UI',
url: 'https://ui.lobehub.com',
},
{
description: 'AI Commit CLI',
openExternal: true,
title: '💌 Lobe Commit',
url: 'https://github.com/lobehub/lobe-commit',
},
];

View File

@ -1,4 +1,5 @@
import { Footer as F } from '@lobehub/ui';
import { consola } from 'consola';
import isEqual from 'fast-deep-equal';
import { memo, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
@ -10,54 +11,57 @@ import { Community, Help, MoreProducts, Resources } from './data';
import { useStyles } from './style';
const Footer = memo<DivProps>(({ className, ...props }) => {
const setting = useAppStore(selectors.currentSetting, isEqual);
const { cx, styles } = useStyles();
const { t } = useTranslation();
const footerReference = useRef<HTMLDivElement>(null);
const setting = useAppStore(selectors.currentSetting, isEqual);
const { cx, styles } = useStyles();
const { t } = useTranslation();
const footerReference = useRef<HTMLDivElement>(null);
useEffect(() => {
console.time('🤯 [layout] inject - Footer');
const footer = gradioApp().querySelector('#footer');
if (footer) footerReference.current?.append(footer);
if (setting.confirmPageUnload) {
window.addEventListener('beforeunload', (event) => {
if (footer?.isConnected) {
event.preventDefault();
return (event.returnValue = '');
useEffect(() => {
try {
const footer = gradioApp().querySelector('#footer');
if (footer) footerReference.current?.append(footer);
if (setting.confirmPageUnload) {
window.addEventListener('beforeunload', (event) => {
if (footer?.isConnected) {
event.preventDefault();
return (event.returnValue = '');
}
});
}
consola.success('🤯 [layout] inject - Footer');
} catch (error) {
consola.error('🤯 [layout] inject - Footer', error);
}
});
}
console.timeEnd('🤯 [layout] inject - Footer');
}, []);
return (
<div className={cx(styles.footer, className)} {...props}>
<F
bottom={<div ref={footerReference} />}
columns={
setting.layoutHideFooter ?
[] :
[
{
items: Resources,
title: t('resources'),
},
{
items: Community,
title: t('community'),
},
{
items: Help,
title: t('help'),
},
{
items: MoreProducts,
title: t('moreProducts'),
},
]
}
/>
</div>
);
}, []);
return (
<div className={cx(styles.footer, className)} {...props}>
<F
bottom={<div ref={footerReference} />}
columns={
setting.layoutHideFooter ?
[] :
[
{
items: Resources,
title: t('footer.resources'),
},
{
items: Community,
title: t('footer.community'),
},
{
items: Help,
title: t('footer.help'),
},
{
items: MoreProducts,
title: t('footer.moreProducts'),
},
]
}
/>
</div>
);
});
export default Footer;

View File

@ -1,7 +1,7 @@
import { createStyles } from 'antd-style';
export const useStyles = createStyles(({ css }) => ({
footer: css`
footer: css`
footer {
display: block !important;
}

View File

@ -5,16 +5,15 @@ import { Github, LayoutGrid, LucideIcon, Moon, Settings, Sun } from 'lucide-reac
import qs from 'query-string';
import { memo, useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { shallow } from 'zustand/shallow';
import { Giscus } from '@/components';
import Setting from '@/features/Setting';
import { selectors, useAppStore } from '@/store';
const CivitaiLogo: LucideIcon | any = ({ size }: any) => (
<svg fill="currentColor" height={size} viewBox="0 0 16 16" width={size}>
<path d="M2 4.5L8 1l6 3.5v7L8 15l-6-3.5v-7zm6-1.194L3.976 5.653v4.694L8 12.694l4.024-2.347V5.653L8 3.306zm0 1.589l2.662 1.552v.824H9.25L8 6.54l-1.25.73v1.458L8 9.46l1.25-.73h1.412v.824L8 11.105 5.338 9.553V6.447L8 4.895z" />
</svg>
<svg fill="currentColor" height={size} viewBox="0 0 16 16" width={size}>
<path d="M2 4.5L8 1l6 3.5v7L8 15l-6-3.5v-7zm6-1.194L3.976 5.653v4.694L8 12.694l4.024-2.347V5.653L8 3.306zm0 1.589l2.662 1.552v.824H9.25L8 6.54l-1.25.73v1.458L8 9.46l1.25-.73h1.412v.824L8 11.105 5.338 9.553V6.447L8 4.895z" />
</svg>
);
interface ActionsProps {
@ -22,48 +21,56 @@ interface ActionsProps {
}
const Actions = memo<ActionsProps>(() => {
const [isSettingOpen, setIsSettingOpen] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false);
const themeMode = useAppStore(selectors.themeMode, shallow);
const { mobile } = useResponsive();
const { t } = useTranslation();
const [isSettingOpen, setIsSettingOpen] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false);
const themeMode = useAppStore(selectors.themeMode);
const { mobile } = useResponsive();
const { t } = useTranslation();
const handleSetTheme = useCallback(() => {
const theme = themeMode === 'light' ? 'dark' : 'light';
const gradioURL = qs.parseUrl(window.location.href);
gradioURL.query.__theme = theme;
window.location.replace(qs.stringifyUrl(gradioURL));
}, [themeMode]);
const handleSetTheme = useCallback(() => {
const theme = themeMode === 'light' ? 'dark' : 'light';
const gradioURL = qs.parseUrl(window.location.href);
gradioURL.query.__theme = theme;
window.location.replace(qs.stringifyUrl(gradioURL));
}, [themeMode]);
return (
<>
<Space.Compact>
{!mobile && (
<>
<a href="https://civitai.com/" rel="noreferrer" target="_blank">
<ActionIcon icon={CivitaiLogo} title="Civitai" />
</a>
<a
href="https://supagruen.github.io/StableDiffusion-CheatSheet/"
rel="noreferrer"
target="_blank"
>
<ActionIcon icon={LayoutGrid} title="Cheat Sheet" />
</a>
<ActionIcon icon={Github} onClick={() => setIsModalOpen(true)} title={t('feedback')} />
</>
)}
<ActionIcon
icon={themeMode === 'light' ? Sun : Moon}
onClick={handleSetTheme}
title={t('switchTheme')}
/>
<ActionIcon icon={Settings} onClick={() => setIsSettingOpen(true)} title={t('setting')} />
</Space.Compact>
<Setting onCancel={() => setIsSettingOpen(false)} open={isSettingOpen} />
<Giscus onCancel={() => setIsModalOpen(false)} open={isModalOpen} />
</>
);
return (
<>
<Space.Compact>
{!mobile && (
<>
<a href="https://civitai.com/" rel="noreferrer" target="_blank">
<ActionIcon icon={CivitaiLogo} title="Civitai" />
</a>
<a
href="https://supagruen.github.io/StableDiffusion-CheatSheet/"
rel="noreferrer"
target="_blank"
>
<ActionIcon icon={LayoutGrid} title="Cheat Sheet" />
</a>
<ActionIcon
icon={Github}
onClick={() => setIsModalOpen(true)}
title={t('header.feedback')}
/>
</>
)}
<ActionIcon
icon={themeMode === 'light' ? Sun : Moon}
onClick={handleSetTheme}
title={t('header.switchTheme')}
/>
<ActionIcon
icon={Settings}
onClick={() => setIsSettingOpen(true)}
title={t('header.setting')}
/>
</Space.Compact>
<Setting onCancel={() => setIsSettingOpen(false)} open={isSettingOpen} />
<Giscus onCancel={() => setIsModalOpen(false)} open={isModalOpen} />
</>
);
});
export default Actions;

View File

@ -1,26 +1,26 @@
import { Burger, TabsNav, type TabsNavProps } from '@lobehub/ui';
import { useResponsive } from 'antd-style';
import { consola } from 'consola';
import { startCase } from 'lodash-es';
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
import { shallow } from 'zustand/shallow';
import { selectors, useAppStore } from '@/store';
const hideOriganlNav = () => {
(gradioApp().querySelector('#tabs > .tab-nav:first-of-type') as HTMLDivElement).style.display =
(gradioApp().querySelector('#tabs > .tab-nav:first-of-type') as HTMLDivElement).style.display =
'none';
};
const getNavTabs = (): HTMLDivElement[] =>
Array.prototype.slice.call(
Array.prototype.slice.call(
gradioApp().querySelectorAll('#tabs > [id^="tab_"]') as NodeListOf<HTMLDivElement>,
);
);
const getNavButtons = (): HTMLButtonElement[] =>
Array.prototype.slice.call(
Array.prototype.slice.call(
gradioApp().querySelectorAll(
'#tabs > .tab-nav:first-of-type button',
'#tabs > .tab-nav:first-of-type button',
) as NodeListOf<HTMLButtonElement>,
);
);
interface NavItem {
id: string;
@ -28,53 +28,56 @@ interface NavItem {
label: string;
}
const genNavList = (): NavItem[] => {
console.debug('🤯 [nav] generate nav list');
const navList = getNavTabs();
const buttons = getNavButtons();
return buttons.map((button, index) => {
const id = navList[index].id;
return {
id,
index,
label: startCase(String(button.textContent)),
};
});
const navList = getNavTabs();
const buttons = getNavButtons();
consola.debug('🤯 [nav] generate nav list');
return buttons.map((button, index) => {
const id = navList[index].id;
return {
id,
index,
label: startCase(String(button.textContent)),
};
});
};
const Nav = memo(() => {
const currentTab = useAppStore(selectors.currentTab, shallow);
const { mobile } = useResponsive();
const [opened, setOpened] = useState(false);
const [items, setItems] = useState<TabsNavProps['items']>([]);
const currentTab = useAppStore(selectors.currentTab);
const { mobile } = useResponsive();
const [opened, setOpened] = useState(false);
const [items, setItems] = useState<TabsNavProps['items']>([]);
const navList = useMemo(() => genNavList(), []);
const navList = useMemo(() => genNavList(), []);
const onChange: TabsNavProps['onChange'] = useCallback(
(id: string) => {
console.debug('🤯 [nav] onClick', id);
const index = navList.find((nav) => nav.id === id)?.index || 0;
const buttonList = getNavButtons();
buttonList[index].click();
},
[navList],
);
const onChange: TabsNavProps['onChange'] = useCallback(
(id: string) => {
consola.debug('🤯 [nav] onClick', id);
const index = navList.find((nav) => nav.id === id)?.index || 0;
const buttonList = getNavButtons();
buttonList[index].click();
},
[navList],
);
useEffect(() => {
console.time('🤯 [layout] inject - Header');
hideOriganlNav();
const list: TabsNavProps['items'] = navList.map((item) => {
return {
key: item.id,
label: mobile ? <div onClick={() => onChange(item.id)}>{item.label}</div> : item.label,
};
});
setItems(list.filter(Boolean));
console.timeEnd('🤯 [layout] inject - Header');
}, [mobile]);
useEffect(() => {
try {
hideOriganlNav();
const list: TabsNavProps['items'] = navList.map((item) => {
return {
key: item.id,
label: mobile ? <div onClick={() => onChange(item.id)}>{item.label}</div> : item.label,
};
});
setItems(list.filter(Boolean));
consola.success('🤯 [layout] inject - Header');
} catch (error) {
consola.error('🤯 [layout] inject - Header', error);
}
}, [mobile]);
if (mobile) return <Burger items={items} opened={opened} setOpened={setOpened} />;
if (mobile) return <Burger items={items} opened={opened} setOpened={setOpened} />;
return <TabsNav activeKey={currentTab} items={items} onChange={onChange} />;
return <TabsNav activeKey={currentTab} items={items} onChange={onChange} />;
});
export default Nav;

View File

@ -12,36 +12,36 @@ import Actions from './Actions';
import Nav from './Nav';
const Header = memo<DivProps>(({ children }) => {
const { themeMode, version } = useAppStore(
(st) => ({ themeMode: st.themeMode, version: st.version }),
shallow,
);
const theme = useTheme();
const { themeMode, version } = useAppStore(
(st) => ({ themeMode: st.themeMode, version: st.version }),
shallow,
);
const theme = useTheme();
return (
<H
actions={<Actions themeMode={themeMode} />}
actionsStyle={{ flex: 0 }}
logo={
<a
href={`${homepage}/releases`}
rel="noreferrer"
style={{ alignItems: 'center', color: theme.colorText, display: 'flex' }}
target="_blank"
>
<Tooltip title={`${name} v${version}`}>
<Logo />
</Tooltip>
</a>
}
nav={
<>
<Nav />
{children}
</>
}
/>
);
return (
<H
actions={<Actions themeMode={themeMode} />}
actionsStyle={{ flex: 0 }}
logo={
<a
href={`${homepage}/releases`}
rel="noreferrer"
style={{ alignItems: 'center', color: theme.colorText, display: 'flex' }}
target="_blank"
>
<Tooltip title={`${name} v${version}`}>
<Logo />
</Tooltip>
</a>
}
nav={
<>
<Nav />
{children}
</>
}
/>
);
});
export default Header;

View File

@ -1,4 +1,5 @@
import { DraggablePanelBody } from '@lobehub/ui';
import { consola } from 'consola';
import isEqual from 'fast-deep-equal';
import { memo, useEffect, useRef } from 'react';
@ -7,22 +8,21 @@ import { selectors, useAppStore } from '@/store';
import { type DivProps } from '@/types';
const Inner = memo<DivProps>(() => {
const setting = useAppStore(selectors.currentSetting, isEqual);
const sidebarReference = useRef<HTMLDivElement>(null);
const setting = useAppStore(selectors.currentSetting, isEqual);
const sidebarReference = useRef<HTMLDivElement>(null);
useEffect(() => {
console.time('🤯 [layout] inject - QuickSettingSidebar');
const sidebar = gradioApp().querySelector('#quicksettings');
if (sidebar) sidebarReference.current?.append(sidebar);
console.timeEnd('🤯 [layout] inject - QuickSettingSidebar');
}, []);
useEffect(() => {
const sidebar = gradioApp().querySelector('#quicksettings');
if (sidebar) sidebarReference.current?.append(sidebar);
consola.success('🤯 [layout] inject - QuickSettingSidebar');
}, []);
return (
<DraggablePanelBody>
{setting.promptEditor && <PromptEditor />}
<div ref={sidebarReference} />
</DraggablePanelBody>
);
return (
<DraggablePanelBody>
{setting.promptEditor && <PromptEditor />}
<div ref={sidebarReference} />
</DraggablePanelBody>
);
});
export default Inner;

View File

@ -1,9 +1,9 @@
import {
type DivProps,
DraggablePanel,
DraggablePanelContainer,
DraggablePanelHeader,
LayoutSidebarInner,
type DivProps,
DraggablePanel,
DraggablePanelContainer,
DraggablePanelHeader,
LayoutSidebarInner,
} from '@lobehub/ui';
import { useResponsive } from 'antd-style';
import isEqual from 'fast-deep-equal';
@ -20,56 +20,56 @@ export interface QuickSettingSidebarProps extends DivProps {
}
const QuickSettingSidebar = memo<QuickSettingSidebarProps>(({ headerHeight }) => {
const { mobile } = useResponsive();
const setting = useAppStore(selectors.currentSetting, isEqual);
const [expand, setExpand] = useState<boolean>(mobile ? false : setting.sidebarExpand);
const [pin, setPin] = useState<boolean>(setting.sidebarFixedMode === 'fixed');
const [width, setWidth] = useState<number>(setting.sidebarWidth);
const { styles, theme } = useStyles({ headerHeight, width });
const { t } = useTranslation();
const { mobile } = useResponsive();
const setting = useAppStore(selectors.currentSetting, isEqual);
const [expand, setExpand] = useState<boolean>(mobile ? false : setting.sidebarExpand);
const [pin, setPin] = useState<boolean>(setting.sidebarFixedMode === 'fixed');
const [width, setWidth] = useState<number>(setting.sidebarWidth);
const { styles, theme } = useStyles({ headerHeight, width });
const { t } = useTranslation();
useEffect(() => {
if (mobile) setExpand(false);
}, [mobile]);
useEffect(() => {
if (mobile) setExpand(false);
}, [mobile]);
const mode = mobile ? 'fixed' : pin ? 'fixed' : 'float';
const mode = mobile ? 'fixed' : pin ? 'fixed' : 'float';
return (
<DraggablePanel
defaultSize={{ width: setting.sidebarWidth }}
expand={expand}
minWidth={setting.sidebarWidth}
mode={mode}
onExpandChange={setExpand}
onSizeChange={(_, size) => size?.width && setWidth(Number.parseInt(String(size.width)))}
pin={pin}
placement="left"
style={{
display: 'flex',
flexDirection: 'column',
}}
>
<LayoutSidebarInner>
<DraggablePanelContainer
className={styles.container}
style={
mode === 'float' ?
{ background: theme.colorBgContainer, minWidth: setting.sidebarWidth } :
{ minWidth: setting.sidebarWidth }
}
>
<DraggablePanelHeader
return (
<DraggablePanel
defaultSize={{ width: setting.sidebarWidth }}
expand={expand}
minWidth={setting.sidebarWidth}
mode={mode}
onExpandChange={setExpand}
onSizeChange={(_, size) => size?.width && setWidth(Number.parseInt(String(size.width)))}
pin={pin}
position="left"
setExpand={setExpand}
setPin={setPin}
title={t('quickSetting')}
/>
<Inner />
</DraggablePanelContainer>
</LayoutSidebarInner>
</DraggablePanel>
);
placement="left"
style={{
display: 'flex',
flexDirection: 'column',
}}
>
<LayoutSidebarInner>
<DraggablePanelContainer
className={styles.container}
style={
mode === 'float' ?
{ background: theme.colorBgContainer, minWidth: setting.sidebarWidth } :
{ minWidth: setting.sidebarWidth }
}
>
<DraggablePanelHeader
pin={pin}
position="left"
setExpand={setExpand}
setPin={setPin}
title={t('sidebar.quickSetting')}
/>
<Inner />
</DraggablePanelContainer>
</LayoutSidebarInner>
</DraggablePanel>
);
});
export default QuickSettingSidebar;

View File

@ -1,8 +1,8 @@
import { createStyles } from 'antd-style';
export const useStyles = createStyles(
({ css }, { headerHeight = 64, width }: { headerHeight?: number; width: number }) => ({
container: css`
({ css }, { headerHeight = 64, width }: { headerHeight?: number; width: number }) => ({
container: css`
height: calc(100vh - ${headerHeight}px);
ul.options {
@ -68,5 +68,5 @@ export const useStyles = createStyles(
}
}
`,
}),
}),
);

View File

@ -8,21 +8,21 @@ import { shallow } from 'zustand/shallow';
import { CustomLogo } from '@/components';
import {
DEFAULT_SETTING,
type WebuiSetting,
type WebuiSettingKeys,
selectors,
useAppStore,
DEFAULT_SETTING,
type WebuiSetting,
type WebuiSettingKeys,
selectors,
useAppStore,
} from '@/store';
import {
type NeutralColor,
type PrimaryColor,
findCustomThemeName,
neutralColors,
neutralColorsSwatches,
primaryColors,
primaryColorsSwatches,
type NeutralColor,
type PrimaryColor,
findCustomThemeName,
neutralColors,
neutralColorsSwatches,
primaryColors,
primaryColorsSwatches,
} from './data';
import { useStyles } from './style';
@ -33,353 +33,353 @@ type SettingItemGroup = ItemGroup & {
};
const SettingForm = memo(() => {
const setting = useAppStore(selectors.currentSetting, isEqual);
const { onSetSetting, localeOptions } = useAppStore(
(st) => ({ localeOptions: st.localeOptions, onSetSetting: st.onSetSetting }),
shallow,
);
const [rawSetting, setRawSetting] = useState<WebuiSetting>(setting);
const [primaryColor, setPrimaryColor] = useState<PrimaryColor | undefined>(
setting.primaryColor || undefined,
);
const [neutralColor, setNeutralColor] = useState<NeutralColor | undefined>(
setting.neutralColor || undefined,
);
const { styles } = useStyles();
const { t } = useTranslation();
const setting = useAppStore(selectors.currentSetting, isEqual);
const { onSetSetting, localeOptions } = useAppStore(
(st) => ({ localeOptions: st.localeOptions, onSetSetting: st.onSetSetting }),
shallow,
);
const [rawSetting, setRawSetting] = useState<WebuiSetting>(setting);
const [primaryColor, setPrimaryColor] = useState<PrimaryColor | undefined>(
setting.primaryColor || undefined,
);
const [neutralColor, setNeutralColor] = useState<NeutralColor | undefined>(
setting.neutralColor || undefined,
);
const { styles } = useStyles();
const { t } = useTranslation();
const onReset = useCallback(() => {
onSetSetting(DEFAULT_SETTING);
location.reload();
}, []);
const onReset = useCallback(() => {
onSetSetting(DEFAULT_SETTING);
location.reload();
}, []);
const onFinish = useCallback(
(value: WebuiSetting) => {
onSetSetting({ ...value, neutralColor, primaryColor });
location.reload();
},
[primaryColor, neutralColor],
);
const onFinish = useCallback(
(value: WebuiSetting) => {
onSetSetting({ ...value, neutralColor, primaryColor });
location.reload();
},
[primaryColor, neutralColor],
);
const theme: SettingItemGroup = useMemo(
() => ({
children: [
{
children: <Select options={localeOptions} />,
desc: t('settingLanguageDesc'),
label: t('settingLanguage'),
name: 'i18n',
},
{
children: <Switch />,
desc: t('settingReduceAnimationDesc'),
label: t('settingReduceAnimation'),
name: 'liteAnimation',
valuePropName: 'checked',
},
{
children: (
<Swatches
activeColor={primaryColor ? primaryColors[primaryColor] : undefined}
colors={primaryColorsSwatches}
onSelect={(c) => setPrimaryColor(findCustomThemeName('primary', c))}
/>
),
desc: t('settingPrimaryColorDesc'),
label: t('settingPrimaryColor'),
},
{
children: (
<Swatches
activeColor={neutralColor ? neutralColors[neutralColor] : undefined}
colors={neutralColorsSwatches}
onSelect={(c) => setNeutralColor(findCustomThemeName('neutral', c))}
/>
),
desc: t('settingNeutralColorDesc'),
label: t('settingNeutralColor'),
},
{
children: (
<Segmented
options={[
const theme: SettingItemGroup = useMemo(
() => ({
children: [
{
label: t('lobe'),
value: 'lobe',
children: <Select options={localeOptions} />,
desc: t('setting.language.desc'),
label: t('setting.language.title'),
name: 'i18n',
},
{
label: t('kitchen'),
value: 'kitchen',
children: <Switch />,
desc: t('setting.reduceAnimation.desc'),
label: t('setting.reduceAnimation.title'),
name: 'liteAnimation',
valuePropName: 'checked',
},
{
label: t('custom'),
value: 'custom',
children: (
<Swatches
activeColor={primaryColor ? primaryColors[primaryColor] : undefined}
colors={primaryColorsSwatches}
onSelect={(c) => setPrimaryColor(findCustomThemeName('primary', c))}
/>
),
desc: t('setting.primaryColor.desc'),
label: t('setting.primaryColor.title'),
},
]}
/>
),
desc: t('settingLogoTypeDesc'),
label: t('settingLogoType'),
name: 'logoType',
},
{
children: <Input />,
desc: t('settingCustomLogoDesc'),
divider: false,
hidden: rawSetting.logoType !== 'custom',
label: t('settingCustomLogo'),
name: 'logoCustomUrl',
},
{
children: <Input />,
desc: t('settingCustomTitleDesc'),
divider: false,
hidden: rawSetting.logoType !== 'custom',
label: t('settingCustomTitle'),
name: 'logoCustomTitle',
},
{
children: (
<CustomLogo
logoCustomTitle={rawSetting.logoCustomTitle}
logoCustomUrl={rawSetting.logoCustomUrl}
/>
),
divider: false,
hidden: rawSetting.logoType !== 'custom',
label: t('settingLogoPreview'),
},
{
children: <Switch />,
desc: t('settingSvgIconsDesc'),
label: t('settingSvgIcons'),
name: 'svgIcon',
valuePropName: 'checked',
},
{
children: <Switch />,
desc: t('settingCustomFontDesc'),
label: t('settingCustomFont'),
name: 'enableWebFont',
valuePropName: 'checked',
},
{
children: <Switch />,
desc: t('settingConfirmPageUnloadDesc'),
label: t('settingConfirmPageUnload'),
name: 'confirmPageUnload',
valuePropName: 'checked',
},
],
icon: Palette,
title: t('settingGroupTheme'),
}),
[
primaryColor,
neutralColor,
rawSetting.logoType,
rawSetting.logoCustomTitle,
rawSetting.logoCustomUrl,
],
);
{
children: (
<Swatches
activeColor={neutralColor ? neutralColors[neutralColor] : undefined}
colors={neutralColorsSwatches}
onSelect={(c) => setNeutralColor(findCustomThemeName('neutral', c))}
/>
),
desc: t('setting.neutralColor.desc'),
label: t('setting.neutralColor.title'),
},
{
children: (
<Segmented
options={[
{
label: t('brand.lobe'),
value: 'lobe',
},
{
label: t('brand.kitchen'),
value: 'kitchen',
},
{
label: t('brand.custom'),
value: 'custom',
},
]}
/>
),
desc: t('setting.logoType.desc'),
label: t('setting.logoType.title'),
name: 'logoType',
},
{
children: <Input />,
desc: t('setting.customLogo.desc'),
divider: false,
hidden: rawSetting.logoType !== 'custom',
label: t('setting.customLogo.title'),
name: 'logoCustomUrl',
},
{
children: <Input />,
desc: t('setting.customTitle.desc'),
divider: false,
hidden: rawSetting.logoType !== 'custom',
label: t('setting.customTitle.title'),
name: 'logoCustomTitle',
},
{
children: (
<CustomLogo
logoCustomTitle={rawSetting.logoCustomTitle}
logoCustomUrl={rawSetting.logoCustomUrl}
/>
),
divider: false,
hidden: rawSetting.logoType !== 'custom',
label: t('setting.logoType.preview'),
},
{
children: <Switch />,
desc: t('setting.svgIcons.desc'),
label: t('setting.svgIcons.title'),
name: 'svgIcon',
valuePropName: 'checked',
},
{
children: <Switch />,
desc: t('setting.customFont.desc'),
label: t('setting.customFont.title'),
name: 'enableWebFont',
valuePropName: 'checked',
},
{
children: <Switch />,
desc: t('setting.confirmPageUnload.desc'),
label: t('setting.confirmPageUnload.title'),
name: 'confirmPageUnload',
valuePropName: 'checked',
},
],
icon: Palette,
title: t('setting.group.theme'),
}),
[
primaryColor,
neutralColor,
rawSetting.logoType,
rawSetting.logoCustomTitle,
rawSetting.logoCustomUrl,
],
);
const promptTextarea: SettingItemGroup = useMemo(
() => ({
children: [
{
children: (
<Segmented
options={[
const promptTextarea: SettingItemGroup = useMemo(
() => ({
children: [
{
label: t('scroll'),
value: 'scroll',
children: (
<Segmented
options={[
{
label: t('setting.promptDisplayMode.scroll'),
value: 'scroll',
},
{
label: t('setting.promptDisplayMode.resizable'),
value: 'resizable',
},
]}
/>
),
desc: t('setting.promptDisplayMode.desc'),
label: t('setting.promptDisplayMode.title'),
name: 'promptTextareaType',
},
{
label: t('resizable'),
value: 'resizable',
children: <Switch />,
desc: t('setting.promptHighlight.desc'),
label: t('setting.promptHighlight.title'),
name: 'enableHighlight',
valuePropName: 'checked',
},
]}
/>
),
desc: t('settingPromptDisplayModeDesc'),
label: t('settingPromptDisplayMode'),
name: 'promptTextareaType',
},
{
children: <Switch />,
desc: t('settingPromptHighlightDesc'),
label: t('settingPromptHighlight'),
name: 'enableHighlight',
valuePropName: 'checked',
},
{
children: <Switch />,
desc: t('settingPromptEditorDesc'),
label: t('settingPromptEditor'),
name: 'promptEditor',
valuePropName: 'checked',
},
],
icon: TextCursorInput,
title: t('settingGroupPromptTextarea'),
}),
[],
);
{
children: <Switch />,
desc: t('setting.promptEditor.desc'),
label: t('setting.promptEditor.title'),
name: 'promptEditor',
valuePropName: 'checked',
},
],
icon: TextCursorInput,
title: t('setting.group.promptTextarea'),
}),
[],
);
const layout: SettingItemGroup = useMemo(
() => ({
children: [
{
children: <Switch />,
desc: t('settingSplitPreviewerDesc'),
label: t('settingSplitPreviewer'),
name: 'layoutSplitPreview',
valuePropName: 'checked',
},
{
children: <Switch />,
desc: t('settingHideFooterDesc'),
label: t('settingHideFooter'),
name: 'layoutHideFooter',
valuePropName: 'checked',
},
],
icon: Layout,
title: t('settingGroupLayout'),
}),
[],
);
const layout: SettingItemGroup = useMemo(
() => ({
children: [
{
children: <Switch />,
desc: t('setting.splitPreviewer.desc'),
label: t('setting.splitPreviewer.title'),
name: 'layoutSplitPreview',
valuePropName: 'checked',
},
{
children: <Switch />,
desc: t('setting.hideFooter.desc'),
label: t('setting.hideFooter.title'),
name: 'layoutHideFooter',
valuePropName: 'checked',
},
],
icon: Layout,
title: t('setting.group.layout'),
}),
[],
);
const quickSettingSidebar: SettingItemGroup = useMemo(
() => ({
children: [
{
children: <Switch />,
desc: t('settingQuickSettingSidebarEnableDesc'),
label: t('settingQuickSettingSidebarEnable'),
name: 'enableSidebar',
valuePropName: 'checked',
},
{
children: <Switch />,
desc: t('settingQuickSettingSidebarDefaultExpandDesc'),
hidden: !rawSetting.enableSidebar,
label: t('settingQuickSettingSidebarDefaultExpand'),
name: 'sidebarExpand',
valuePropName: 'checked',
},
{
children: (
<Segmented
options={[
const quickSettingSidebar: SettingItemGroup = useMemo(
() => ({
children: [
{
label: t('fixed'),
value: 'fixed',
children: <Switch />,
desc: t('setting.quickSettingSidebar.displayMode.desc'),
label: t('setting.quickSettingSidebar.displayMode.title'),
name: 'enableSidebar',
valuePropName: 'checked',
},
{
label: t('float'),
value: 'float',
children: <Switch />,
desc: t('setting.quickSettingSidebar.defaultExpand.desc'),
hidden: !rawSetting.enableSidebar,
label: t('setting.quickSettingSidebar.defaultExpand.title'),
name: 'sidebarExpand',
valuePropName: 'checked',
},
]}
/>
),
desc: t('settingQuickSettingSidebarDisplayModeDesc'),
hidden: !rawSetting.enableSidebar,
label: t('settingQuickSettingSidebarDisplayMode'),
name: 'sidebarFixedMode',
},
{
children: <InputNumber />,
desc: t('settingQuickSettingSidebarDefaultWidthDesc'),
hidden: !rawSetting.enableSidebar,
label: t('settingQuickSettingSidebarDefaultWidth'),
name: 'sidebarWidth',
},
],
icon: PanelLeftClose,
title: t('settingGroupQuickSettingSidebar'),
}),
[rawSetting.enableSidebar],
);
{
children: (
<Segmented
options={[
{
label: t('sidebar.mode.fixed'),
value: 'fixed',
},
{
label: t('sidebar.mode.float'),
value: 'float',
},
]}
/>
),
desc: t('setting.quickSettingSidebar.displayMode.desc'),
hidden: !rawSetting.enableSidebar,
label: t('setting.quickSettingSidebar.displayMode.title'),
name: 'sidebarFixedMode',
},
{
children: <InputNumber />,
desc: t('setting.quickSettingSidebar.defaultWidth.desc'),
hidden: !rawSetting.enableSidebar,
label: t('setting.quickSettingSidebar.defaultWidth.title'),
name: 'sidebarWidth',
},
],
icon: PanelLeftClose,
title: t('setting.group.quickSettingSidebar'),
}),
[rawSetting.enableSidebar],
);
const extraNetworkSidebar: SettingItemGroup = useMemo(
() => ({
children: [
{
children: <Switch />,
desc: t('settingExtraNetworkSidebarEnableDesc'),
label: t('settingExtraNetworkSidebarEnable'),
name: 'enableExtraNetworkSidebar',
valuePropName: 'checked',
},
{
children: (
<Segmented
options={[
const extraNetworkSidebar: SettingItemGroup = useMemo(
() => ({
children: [
{
label: t('fixed'),
value: 'fixed',
children: <Switch />,
desc: t('setting.extraNetworkSidebar.enable.desc'),
label: t('setting.extraNetworkSidebar.enable.title'),
name: 'enableExtraNetworkSidebar',
valuePropName: 'checked',
},
{
label: t('float'),
value: 'float',
children: (
<Segmented
options={[
{
label: t('sidebar.mode.fixed'),
value: 'fixed',
},
{
label: t('sidebar.mode.float'),
value: 'float',
},
]}
/>
),
desc: t('setting.extraNetworkSidebar.displayMode.desc'),
hidden: !rawSetting.enableExtraNetworkSidebar,
label: t('setting.extraNetworkSidebar.displayMode.title'),
name: 'extraNetworkFixedMode',
},
]}
/>
),
desc: t('settingExtraNetworkSidebarDisplayModeDesc'),
hidden: !rawSetting.enableExtraNetworkSidebar,
label: t('settingExtraNetworkSidebarDisplayMode'),
name: 'extraNetworkFixedMode',
},
{
children: <Switch />,
desc: t('settingExtraNetworkSidebarDefaultExpandDesc'),
hidden: !rawSetting.enableExtraNetworkSidebar,
label: t('settingExtraNetworkSidebarDefaultExpand'),
name: 'extraNetworkSidebarExpand',
valuePropName: 'checked',
},
{
children: <InputNumber />,
desc: t('settingExtraNetworkSidebarDefaultWidthDesc'),
hidden: !rawSetting.enableExtraNetworkSidebar,
label: t('settingExtraNetworkSidebarDefaultWidth'),
name: 'extraNetworkSidebarWidth',
},
{
children: <InputNumber />,
desc: t('settingExtraNetworkSidebarDefaultCardSizeDesc'),
hidden: !rawSetting.enableExtraNetworkSidebar,
label: t('settingExtraNetworkSidebarDefaultCardSize'),
name: 'extraNetworkCardSize',
},
],
icon: PanelRightClose,
title: t('settingGroupExtraNetworkSidebar'),
}),
[rawSetting.enableExtraNetworkSidebar],
);
{
children: <Switch />,
desc: t('setting.extraNetworkSidebar.defaultExpand.desc'),
hidden: !rawSetting.enableExtraNetworkSidebar,
label: t('setting.extraNetworkSidebar.defaultExpand.title'),
name: 'extraNetworkSidebarExpand',
valuePropName: 'checked',
},
{
children: <InputNumber />,
desc: t('setting.extraNetworkSidebar.defaultWidth.desc'),
hidden: !rawSetting.enableExtraNetworkSidebar,
label: t('setting.extraNetworkSidebar.defaultWidth.title'),
name: 'extraNetworkSidebarWidth',
},
{
children: <InputNumber />,
desc: t('setting.extraNetworkSidebar.defaultCardSize.desc'),
hidden: !rawSetting.enableExtraNetworkSidebar,
label: t('setting.extraNetworkSidebar.defaultCardSize.title'),
name: 'extraNetworkCardSize',
},
],
icon: PanelRightClose,
title: t('setting.group.extraNetworkSidebar'),
}),
[rawSetting.enableExtraNetworkSidebar],
);
return (
<Form
className={styles}
footer={
<>
<Button htmlType="button" onClick={onReset} style={{ borderRadius: 4 }}>
{t('settingButtonReset')}
</Button>
<Button htmlType="submit" style={{ borderRadius: 4 }} type="primary">
{t('settingButtonSubmit')}
</Button>
</>
}
initialValues={setting}
items={[theme, promptTextarea, layout, quickSettingSidebar, extraNetworkSidebar]}
onFinish={onFinish}
onValuesChange={(_, v) => setRawSetting(v)}
/>
);
return (
<Form
className={styles}
footer={
<>
<Button htmlType="button" onClick={onReset} style={{ borderRadius: 4 }}>
{t('setting.button.reset')}
</Button>
<Button htmlType="submit" style={{ borderRadius: 4 }} type="primary">
{t('setting.button.submit')}
</Button>
</>
}
initialValues={setting}
items={[theme, promptTextarea, layout, quickSettingSidebar, extraNetworkSidebar]}
onFinish={onFinish}
onValuesChange={(_, v) => setRawSetting(v)}
/>
);
});
export default SettingForm;

View File

@ -1,33 +1,33 @@
import {
neutralColors as nc,
neutralColorsSwatches as ncs,
primaryColorsSwatches as pcs,
primaryColors as ps,
neutralColors as nc,
neutralColorsSwatches as ncs,
primaryColorsSwatches as pcs,
primaryColors as ps,
} from '@lobehub/ui';
import { kitchenNeutral, kitchenPrimary } from '@/styles/kitchenColors';
export const primaryColors = {
kitchen: kitchenPrimary.dark.colorPrimary,
...ps,
kitchen: kitchenPrimary.dark.colorPrimary,
...ps,
};
export const primaryColorsSwatches = [primaryColors.kitchen, ...pcs];
export const neutralColors = {
kitchen: kitchenNeutral.dark.colorNeutral,
...nc,
kitchen: kitchenNeutral.dark.colorNeutral,
...nc,
};
export const neutralColorsSwatches = [neutralColors.kitchen, ...ncs];
export const findCustomThemeName = (type: 'primary' | 'neutral', value?: string): any => {
if (!value) return '';
let res = type === 'primary' ? primaryColors : neutralColors;
let result = Object.entries(res).find((item) => {
return item[1] === value;
});
return result === null || result === void 0 ? void 0 : result[0];
if (!value) return '';
let res = type === 'primary' ? primaryColors : neutralColors;
let result = Object.entries(res).find((item) => {
return item[1] === value;
});
return result === null || result === void 0 ? void 0 : result[0];
};
export type PrimaryColor = keyof typeof primaryColors;

View File

@ -16,27 +16,27 @@ export interface SettingProps {
}
const Setting = memo<SettingProps>(({ open, onCancel }) => {
const { t } = useTranslation();
return (
<Modal
footer={false}
onCancel={onCancel}
open={open}
title={
<Flexbox align={'center'} gap={4} horizontal>
<a href={homepage} rel="noreferrer" target="_blank">
<ActionIcon icon={Book} title="Setting Documents" />
</a>
<Space>
{t('themeSetting')}
<VersionTag />
</Space>
</Flexbox>
}
>
<SettingForm />
</Modal>
);
const { t } = useTranslation();
return (
<Modal
footer={false}
onCancel={onCancel}
open={open}
title={
<Flexbox align={'center'} gap={4} horizontal>
<a href={homepage} rel="noreferrer" target="_blank">
<ActionIcon icon={Book} title="Setting Documents" />
</a>
<Space>
{t('modal.themeSetting.title')}
<VersionTag />
</Space>
</Flexbox>
}
>
<SettingForm />
</Modal>
);
});
export default Setting;

View File

@ -1,7 +1,7 @@
import { createStyles } from 'antd-style';
export const useStyles = createStyles(
({ css, responsive }) => css`
({ css, responsive }) => css`
${responsive.mobile} {
.ant-row.ant-form-item-row {
flex-direction: column !important;

View File

@ -1,37 +1,37 @@
import { useEffect, useState } from 'react';
export const useExternalTextareaObserver = (textareaSelector: string) => {
const [value, setValue] = useState('');
const [value, setValue] = useState('');
useEffect(() => {
const observerCallback: MutationCallback = (mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === 'attributes') {
const externalTextarea = document.querySelector(textareaSelector) as HTMLTextAreaElement;
setValue(externalTextarea.value);
useEffect(() => {
const observerCallback: MutationCallback = (mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === 'attributes') {
const externalTextarea = document.querySelector(textareaSelector) as HTMLTextAreaElement;
setValue(externalTextarea.value);
}
}
};
const observerOptions: MutationObserverInit = {
attributes: true,
characterData: true,
childList: true,
subtree: true,
};
const observer = new MutationObserver(observerCallback);
const externalTextarea = document.querySelector(textareaSelector) as HTMLTextAreaElement | null;
if (externalTextarea) {
observer.observe(externalTextarea, observerOptions);
setValue(externalTextarea.value);
}
}
};
const observerOptions: MutationObserverInit = {
attributes: true,
characterData: true,
childList: true,
subtree: true,
};
return () => {
observer.disconnect();
};
}, [textareaSelector]);
const observer = new MutationObserver(observerCallback);
const externalTextarea = document.querySelector(textareaSelector) as HTMLTextAreaElement | null;
if (externalTextarea) {
observer.observe(externalTextarea, observerOptions);
setValue(externalTextarea.value);
}
return () => {
observer.disconnect();
};
}, [textareaSelector]);
return value;
return value;
};

View File

@ -1,29 +1,29 @@
import { useEffect, useState } from 'react';
const checkIsDarkMode = () => {
try {
return window.matchMedia('(prefers-color-scheme: dark)').matches;
} catch {
return false;
}
try {
return window.matchMedia('(prefers-color-scheme: dark)').matches;
} catch {
return false;
}
};
export const useIsDarkMode = () => {
const [isDarkMode, setIsDarkMode] = useState(checkIsDarkMode());
const [isDarkMode, setIsDarkMode] = useState(checkIsDarkMode());
useEffect(() => {
const mqList = window.matchMedia('(prefers-color-scheme: dark)');
useEffect(() => {
const mqList = window.matchMedia('(prefers-color-scheme: dark)');
const listener = (event: any) => {
setIsDarkMode(event.matches);
};
const listener = (event: any) => {
setIsDarkMode(event.matches);
};
mqList.addEventListener('change', listener);
mqList.addEventListener('change', listener);
return () => {
mqList.removeEventListener('change', listener);
};
}, []);
return () => {
mqList.removeEventListener('change', listener);
};
}, []);
return isDarkMode;
return isDarkMode;
};

View File

@ -1,10 +1,10 @@
import {
type DivProps,
ThemeProvider,
colorScales,
generateColorNeutralPalette,
generateColorPalette,
neutralColorScales,
type DivProps,
ThemeProvider,
colorScales,
generateColorNeutralPalette,
generateColorPalette,
neutralColorScales,
} from '@lobehub/ui';
import isEqual from 'fast-deep-equal';
import qs from 'query-string';
@ -16,58 +16,58 @@ import { selectors, useAppStore } from '@/store';
import { kitchenNeutral, kitchenPrimary } from '@/styles/kitchenColors';
const Layout = memo<DivProps>(({ children }) => {
const { onSetThemeMode, themeMode } = useAppStore(
(st) => ({ onInit: st.onInit, onSetThemeMode: st.onSetThemeMode, themeMode: st.themeMode }),
shallow,
);
const setting = useAppStore(selectors.currentSetting, isEqual);
const isDarkMode = useIsDarkMode();
const { onSetThemeMode, themeMode } = useAppStore(
(st) => ({ onInit: st.onInit, onSetThemeMode: st.onSetThemeMode, themeMode: st.themeMode }),
shallow,
);
const setting = useAppStore(selectors.currentSetting, isEqual);
const isDarkMode = useIsDarkMode();
useEffect(() => {
const queryTheme: any = String(qs.parseUrl(window.location.href).query.__theme || '');
if (queryTheme) {
document.body.classList.add(queryTheme);
onSetThemeMode(queryTheme);
} else {
document.body.classList.add(isDarkMode ? 'dark' : 'light');
onSetThemeMode(isDarkMode ? 'dark' : 'light');
}
}, [isDarkMode]);
useEffect(() => {
const queryTheme: any = String(qs.parseUrl(window.location.href).query.__theme || '');
if (queryTheme) {
document.body.classList.add(queryTheme);
onSetThemeMode(queryTheme);
} else {
document.body.classList.add(isDarkMode ? 'dark' : 'light');
onSetThemeMode(isDarkMode ? 'dark' : 'light');
}
}, [isDarkMode]);
const genCustomToken = useCallback(() => {
let primaryTokens = {};
let neutralTokens = {};
if (setting.primaryColor) {
if (setting.primaryColor === 'kitchen') {
primaryTokens = kitchenPrimary[themeMode];
} else {
const scale = colorScales[setting.primaryColor];
primaryTokens = generateColorPalette({ appearance: themeMode, scale, type: 'Primary' });
}
}
if (setting.neutralColor) {
if (setting.neutralColor === 'kitchen') {
neutralTokens = kitchenNeutral[themeMode];
} else {
const scale = neutralColorScales[setting.neutralColor];
neutralTokens = generateColorNeutralPalette({ appearance: themeMode, scale });
}
}
const genCustomToken = useCallback(() => {
let primaryTokens = {};
let neutralTokens = {};
if (setting.primaryColor) {
if (setting.primaryColor === 'kitchen') {
primaryTokens = kitchenPrimary[themeMode];
} else {
const scale = colorScales[setting.primaryColor];
primaryTokens = generateColorPalette({ appearance: themeMode, scale, type: 'Primary' });
}
}
if (setting.neutralColor) {
if (setting.neutralColor === 'kitchen') {
neutralTokens = kitchenNeutral[themeMode];
} else {
const scale = neutralColorScales[setting.neutralColor];
neutralTokens = generateColorNeutralPalette({ appearance: themeMode, scale });
}
}
return { ...primaryTokens, ...neutralTokens };
}, [setting.primaryColor, setting.neutralColor, themeMode]);
return { ...primaryTokens, ...neutralTokens };
}, [setting.primaryColor, setting.neutralColor, themeMode]);
return (
setting && (
<ThemeProvider
customToken={genCustomToken}
enableWebfonts={setting.enableWebFont}
themeMode={themeMode}
>
{children}
</ThemeProvider>
)
);
return (
setting && (
<ThemeProvider
customToken={genCustomToken}
enableWebfonts={setting.enableWebFont}
themeMode={themeMode}
>
{children}
</ThemeProvider>
)
);
});
export default Layout;

View File

@ -7,13 +7,13 @@ import { SETTING_KEY, type WebuiSetting } from '@/store';
const localSetting = JSON.parse(localStorage.getItem(SETTING_KEY) as any) as WebuiSetting;
i18next
.use(initReactI18next)
.use(HttpBackend)
.init<HttpBackendOptions>({
backend: {
loadPath: '/lobe/locales/{{lng}}',
},
debug: process.env.NODE_ENV === 'development',
fallbackLng: 'en_US',
lng: localSetting?.i18n || 'en_US',
});
.use(initReactI18next)
.use(HttpBackend)
.init<HttpBackendOptions>({
backend: {
loadPath: '/lobe/locales/{{lng}}',
},
debug: process.env.NODE_ENV === 'development',
fallbackLng: 'en_US',
lng: localSetting?.i18n || 'en_US',
});

View File

@ -1,3 +1,4 @@
import { consola } from 'consola';
import { createRoot } from 'react-dom/client';
import Page from './app/page';
@ -7,21 +8,20 @@ if (window.global === undefined) window.global = window;
const skipLoad = window.location.href.includes('dev') && process.env.NODE_ENV === 'production';
if (!skipLoad) {
document.addEventListener(
'DOMContentLoaded',
() => {
console.time('🤯 Lobe Theme load in DevMode');
const root = document.createElement('div');
root.setAttribute('id', 'root');
try {
gradioApp()?.append(root);
} catch {
document.querySelector('gradio-app')?.append(root);
}
const client = createRoot(root);
client.render(<Page />);
console.timeEnd('🤯 Lobe Theme load in DevMode');
},
{ once: true },
);
document.addEventListener(
'DOMContentLoaded',
() => {
consola.start(`🤯 Lobe Theme load in ${process.env.NODE_ENV}`);
const root = document.createElement('div');
root.setAttribute('id', 'root');
try {
gradioApp()?.append(root);
} catch {
document.querySelector('gradio-app')?.append(root);
}
const client = createRoot(root);
client.render(<Page />);
},
{ once: true },
);
}

View File

@ -9,15 +9,15 @@ import { themeConfig } from './promptTheme';
import { useStyles } from './style';
const options: any = {
langs: [
{
aliases: ['prompt'],
grammar,
id: 'prompt',
scopeName: 'source.prompt',
},
],
themes: [themeConfig(true), themeConfig(false)],
langs: [
{
aliases: ['prompt'],
grammar,
id: 'prompt',
scopeName: 'source.prompt',
},
],
themes: [themeConfig(true), themeConfig(false)],
};
interface AppProps {
@ -25,63 +25,63 @@ interface AppProps {
}
const App = memo<AppProps>(({ parentId }) => {
const ref: any = useRef(null);
const [prompt, setPrompt] = useState<string>('');
const { styles, theme } = useStyles();
const nativeTextareaValue = useExternalTextareaObserver(`${parentId} label textarea`);
const nativeTextarea = useMemo(
() => gradioApp().querySelector(`${parentId} label textarea`) as HTMLTextAreaElement,
[parentId],
);
const size = useSize(nativeTextarea);
const scroll = useScroll(nativeTextarea);
const ref: any = useRef(null);
const [prompt, setPrompt] = useState<string>('');
const { styles, theme } = useStyles();
const nativeTextareaValue = useExternalTextareaObserver(`${parentId} label textarea`);
const nativeTextarea = useMemo(
() => gradioApp().querySelector(`${parentId} label textarea`) as HTMLTextAreaElement,
[parentId],
);
const size = useSize(nativeTextarea);
const scroll = useScroll(nativeTextarea);
const handlePromptChange = useCallback((event: any) => {
setPrompt(event.target.value);
}, []);
const handlePromptChange = useCallback((event: any) => {
setPrompt(event.target.value);
}, []);
const handlePromptResize = useCallback(() => {
if (nativeTextarea.clientHeight < nativeTextarea.scrollHeight) {
return size?.width === undefined ? '' : size?.width + 6;
} else {
return size?.width === undefined ? '' : size?.width + 2;
}
}, [nativeTextarea.clientWidth]);
const handlePromptResize = useCallback(() => {
if (nativeTextarea.clientHeight < nativeTextarea.scrollHeight) {
return size?.width === undefined ? '' : size?.width + 6;
} else {
return size?.width === undefined ? '' : size?.width + 2;
}
}, [nativeTextarea.clientWidth]);
useEffect(() => {
ref.current.scroll(0, scroll?.top || 0);
}, [scroll?.top]);
useEffect(() => {
ref.current.scroll(0, scroll?.top || 0);
}, [scroll?.top]);
useEffect(() => {
nativeTextarea.addEventListener('change', handlePromptChange);
return () => {
nativeTextarea.removeEventListener('change', handlePromptChange);
};
}, []);
useEffect(() => {
nativeTextarea.addEventListener('change', handlePromptChange);
return () => {
nativeTextarea.removeEventListener('change', handlePromptChange);
};
}, []);
useEffect(() => {
if (theme) {
nativeTextarea.style.color = 'transparent';
nativeTextarea.style.caretColor = theme.colorSuccess;
}
}, [theme]);
useEffect(() => {
if (theme) {
nativeTextarea.style.color = 'transparent';
nativeTextarea.style.caretColor = theme.colorSuccess;
}
}, [theme]);
useEffect(() => {
setPrompt(nativeTextareaValue);
}, [nativeTextareaValue]);
useEffect(() => {
setPrompt(nativeTextareaValue);
}, [nativeTextareaValue]);
return (
<div
className={styles.container}
data-code-type="highlighter"
ref={ref}
style={{ height: size?.height, width: handlePromptResize() }}
>
<SyntaxHighlighter language="prompt" options={options}>
{prompt.trim()}
</SyntaxHighlighter>
</div>
);
return (
<div
className={styles.container}
data-code-type="highlighter"
ref={ref}
style={{ height: size?.height, width: handlePromptResize() }}
>
<SyntaxHighlighter language="prompt" options={options}>
{prompt.trim()}
</SyntaxHighlighter>
</div>
);
});
export default App;

View File

@ -15,35 +15,35 @@ export interface SyntaxHighlighterProps {
}
const SyntaxHighlighter = memo<SyntaxHighlighterProps>(({ children, language, options }) => {
const { styles } = useStyles();
const { isDarkMode } = useThemeMode();
const [codeToHtml, isLoading] = useHighlight((s) => [s.codeToHtml, !s.highlighter]);
const { styles } = useStyles();
const { isDarkMode } = useThemeMode();
const [codeToHtml, isLoading] = useHighlight((s) => [s.codeToHtml, !s.highlighter]);
useEffect(() => {
useHighlight.getState().initHighlighter(options);
}, [options]);
useEffect(() => {
useHighlight.getState().initHighlighter(options);
}, [options]);
return (
<>
{isLoading ? (
<code>{children}</code>
) : (
<div
className={styles.shiki}
dangerouslySetInnerHTML={{
__html: codeToHtml(children, language, isDarkMode) || '',
}}
/>
)}
return (
<>
{isLoading ? (
<code>{children}</code>
) : (
<div
className={styles.shiki}
dangerouslySetInnerHTML={{
__html: codeToHtml(children, language, isDarkMode) || '',
}}
/>
)}
{isLoading && (
<Center className={styles.loading} gap={8} horizontal>
<Icon icon={Loader2} spin />
{isLoading && (
<Center className={styles.loading} gap={8} horizontal>
<Icon icon={Loader2} spin />
Highlighting...
</Center>
)}
</>
);
</Center>
)}
</>
);
});
export default SyntaxHighlighter;

View File

@ -1,6 +1,5 @@
import { StrictMode, Suspense, memo } from 'react';
import { createRoot } from 'react-dom/client';
import { shallow } from 'zustand/shallow';
import Layout from '@/layouts';
import { useAppStore } from '@/store';
@ -8,26 +7,26 @@ import { useAppStore } from '@/store';
import App from './App';
const Main = memo<{ parentId: string }>(({ parentId }) => {
const { loading } = useAppStore((st) => ({ loading: st.loading }), shallow);
const loading = useAppStore((st) => st.loading);
return <Layout>{loading === false && <App parentId={parentId} />}</Layout>;
return <Layout>{loading === false && <App parentId={parentId} />}</Layout>;
});
export const PromptHighlight = (parentId: string, containerId: string) => {
if (document.querySelector(containerId)) return;
const settingsDiv = document.createElement('div') as HTMLDivElement;
settingsDiv.id = containerId.replace('#', '');
if (document.querySelector(containerId)) return;
const settingsDiv = document.createElement('div') as HTMLDivElement;
settingsDiv.id = containerId.replace('#', '');
(gradioApp().querySelector(parentId) as HTMLDivElement).insertBefore(
settingsDiv,
(gradioApp().querySelector(parentId) as HTMLDivElement).firstChild,
);
(gradioApp().querySelector(parentId) as HTMLDivElement).insertBefore(
settingsDiv,
(gradioApp().querySelector(parentId) as HTMLDivElement).firstChild,
);
createRoot(settingsDiv).render(
<StrictMode>
<Suspense fallback="loading...">
<Main parentId={parentId} />
</Suspense>
</StrictMode>,
);
createRoot(settingsDiv).render(
<StrictMode>
<Suspense fallback="loading...">
<Main parentId={parentId} />
</Suspense>
</StrictMode>,
);
};

View File

@ -2,76 +2,76 @@ import { colors as colorScales } from '@lobehub/ui';
import { ThemeAppearance } from 'antd-style';
export const themeConfig: any = (isDarkMode: ThemeAppearance) => {
const type = isDarkMode ? 'dark' : 'light';
const type = isDarkMode ? 'dark' : 'light';
const colorYellow = isDarkMode ? colorScales.yellow[type][9] : colorScales.gold[type][10];
const colorOrange = isDarkMode ? colorScales.gold[type][9] : colorScales.orange[type][10];
const colorVolcano = isDarkMode ? colorScales.volcano[type][10] : colorScales.volcano[type][8];
const colorGreen = isDarkMode ? colorScales.lime[type][9] : colorScales.green[type][10];
const colorBlue = isDarkMode ? colorScales.blue[type][9] : colorScales.geekblue[type][9];
const colorPurple = isDarkMode ? colorScales.purple[type][11] : colorScales.purple[type][8];
return {
colors: {
'editor.foreground': colorGreen,
},
name: type,
tokenColors: [
{
scope: 'comma',
settings: {
foreground: colorGreen,
const colorYellow = isDarkMode ? colorScales.yellow[type][9] : colorScales.gold[type][10];
const colorOrange = isDarkMode ? colorScales.gold[type][9] : colorScales.orange[type][10];
const colorVolcano = isDarkMode ? colorScales.volcano[type][10] : colorScales.volcano[type][8];
const colorGreen = isDarkMode ? colorScales.lime[type][9] : colorScales.green[type][10];
const colorBlue = isDarkMode ? colorScales.blue[type][9] : colorScales.geekblue[type][9];
const colorPurple = isDarkMode ? colorScales.purple[type][11] : colorScales.purple[type][8];
return {
colors: {
'editor.foreground': colorGreen,
},
},
{
scope: 'func',
settings: {
foreground: colorPurple,
},
},
{
scope: ['and', 'break'],
settings: {
foreground: colorBlue,
},
},
{
scope: 'bracket',
settings: {
foreground: colorPurple,
},
},
{
scope: 'model-type',
settings: {
fontStyle: 'italic',
foreground: colorVolcano,
},
},
{
scope: 'model-name',
settings: {
foreground: colorOrange,
},
},
{
scope: 'model-bracket',
settings: {
foreground: colorPurple,
},
},
{
scope: 'number',
settings: {
foreground: colorPurple,
},
},
{
scope: 'wildcards',
settings: {
foreground: colorYellow,
},
},
],
type,
};
name: type,
tokenColors: [
{
scope: 'comma',
settings: {
foreground: colorGreen,
},
},
{
scope: 'func',
settings: {
foreground: colorPurple,
},
},
{
scope: ['and', 'break'],
settings: {
foreground: colorBlue,
},
},
{
scope: 'bracket',
settings: {
foreground: colorPurple,
},
},
{
scope: 'model-type',
settings: {
fontStyle: 'italic',
foreground: colorVolcano,
},
},
{
scope: 'model-name',
settings: {
foreground: colorOrange,
},
},
{
scope: 'model-bracket',
settings: {
foreground: colorPurple,
},
},
{
scope: 'number',
settings: {
foreground: colorPurple,
},
},
{
scope: 'wildcards',
settings: {
foreground: colorYellow,
},
},
],
type,
};
};

View File

@ -1,9 +1,9 @@
import { createStyles } from 'antd-style';
export const useStyles = createStyles(({ css, token, cx, stylish, prefixCls }) => {
const prefix = `${prefixCls}-highlighter`;
return {
container: css`
const prefix = `${prefixCls}-highlighter`;
return {
container: css`
pointer-events: none;
position: absolute;
overflow: hidden auto;
@ -18,9 +18,9 @@ export const useStyles = createStyles(({ css, token, cx, stylish, prefixCls }) =
vertical-align: bottom !important;
}
`,
loading: cx(
stylish.blur,
css`
loading: cx(
stylish.blur,
css`
position: absolute;
z-index: 10;
top: 0;
@ -38,10 +38,10 @@ export const useStyles = createStyles(({ css, token, cx, stylish, prefixCls }) =
border-radius: ${token.borderRadius};
`,
),
shiki: cx(
`${prefix}-shiki`,
css`
),
shiki: cx(
`${prefix}-shiki`,
css`
margin: 0;
.shiki {
@ -55,6 +55,6 @@ export const useStyles = createStyles(({ css, token, cx, stylish, prefixCls }) =
}
}
`,
),
};
),
};
});

View File

@ -28,33 +28,33 @@ interface Store {
}
export const useHighlight = createWithEqualityFn<Store>(
(set, get) => ({
codeToHtml: (text, language, isDarkMode) => {
const { highlighter } = get();
(set, get) => ({
codeToHtml: (text, language, isDarkMode) => {
const { highlighter } = get();
if (!highlighter) return '';
if (!highlighter) return '';
try {
return highlighter?.codeToHtml(text, {
lang: language,
theme: isDarkMode ? 'dark' : 'light',
});
} catch {
return text;
}
},
highlighter: undefined,
try {
return highlighter?.codeToHtml(text, {
lang: language,
theme: isDarkMode ? 'dark' : 'light',
});
} catch {
return text;
}
},
highlighter: undefined,
initHighlighter: async(options) => {
if (!get().highlighter) {
const highlighter = await getHighlighter({
langs: options?.langs,
themes: options?.themes,
});
initHighlighter: async(options) => {
if (!get().highlighter) {
const highlighter = await getHighlighter({
langs: options?.langs,
themes: options?.themes,
});
set({ highlighter });
}
},
}),
shallow,
set({ highlighter });
}
},
}),
shallow,
);

View File

@ -1,17 +1,19 @@
import { consola } from 'consola';
const TAB_PREFIX_LIST = ['txt2img', 'img2img'] as const;
const MODEL_TYPE_LIST = [
'textual_inversion',
'hypernetworks',
'checkpoints',
'lora',
'lycoris',
'textual_inversion',
'hypernetworks',
'checkpoints',
'lora',
'lycoris',
] as const;
const MODEL_TYPE = {
checkpoints: 'ckp',
hypernetworks: 'hyper',
lora: 'lora',
lycoris: 'lycoris',
textual_inversion: 'ti',
checkpoints: 'ckp',
hypernetworks: 'hyper',
lora: 'lora',
lycoris: 'lycoris',
textual_inversion: 'ti',
} as const satisfies Record<(typeof MODEL_TYPE_LIST)[number], string>;
const CARDID_SUFFIX = 'cards' as const;
@ -30,50 +32,50 @@ const DOM_CACHE_KEY = `${DOM_CACHE_PREFIX}Done` as const;
const DOM_CACHE_VALUE = '1' as const;
const styleButton = (node: HTMLElement, isThumbMode: boolean) => {
if (isThumbMode) {
node.style.display = BTN_THUMB_DISPLAY;
node.style.fontSize = BTN_THUMB_FONT_SIZE;
node.style.position = BTN_THUMB_POS;
node.style.backgroundImage = BTN_THUMB_BACKGROUND_IMAGE;
} else {
node.style.fontSize = BTN_FONT_SIZE;
node.style.margin = BTN_MARGIN;
}
if (isThumbMode) {
node.style.display = BTN_THUMB_DISPLAY;
node.style.fontSize = BTN_THUMB_FONT_SIZE;
node.style.position = BTN_THUMB_POS;
node.style.backgroundImage = BTN_THUMB_BACKGROUND_IMAGE;
} else {
node.style.fontSize = BTN_FONT_SIZE;
node.style.margin = BTN_MARGIN;
}
};
type IStrictNullable<T> = T | null;
type INullable<T> = T | null | undefined;
function is_nullable<T>(v: INullable<T>): v is null | undefined {
return v === undefined || v === null;
return v === undefined || v === null;
}
const updateCardForCivitai = () => {
if (!document.querySelector('#tab_civitai_helper')) return;
if (!document.querySelector('#tab_civitai_helper')) return;
const replacePreviewText = getTranslation('replace preview') || 'replace preview';
const replacePreviewText = getTranslation('replace preview') || 'replace preview';
// Get component
const chAlwaysDisplayCkb = document.querySelector(
'#ch_always_display_ckb input',
) as HTMLInputElement;
const chShowButtonOnThumbCkb = document.querySelector(
'#ch_show_btn_on_thumb_ckb input',
) as HTMLInputElement;
const chAlwaysDisplay = chAlwaysDisplayCkb?.checked || false;
const chShowButtonOnThumb = chShowButtonOnThumbCkb?.checked || false;
// Get component
const chAlwaysDisplayCkb = document.querySelector(
'#ch_always_display_ckb input',
) as HTMLInputElement;
const chShowButtonOnThumbCkb = document.querySelector(
'#ch_show_btn_on_thumb_ckb input',
) as HTMLInputElement;
const chAlwaysDisplay = chAlwaysDisplayCkb?.checked || false;
const chShowButtonOnThumb = chShowButtonOnThumbCkb?.checked || false;
// Change all "replace preview" into an icon
let extraNetworkId: `${(typeof TAB_PREFIX_LIST)[number]}_${(typeof MODEL_TYPE_LIST)[number]}_${typeof CARDID_SUFFIX}`;
let extraNetworkNode: IStrictNullable<HTMLElement>;
let metadataButton: IStrictNullable<HTMLElement>;
let additionalNode: HTMLElement;
let replacePreviewButton: IStrictNullable<HTMLElement>;
let ulNode: IStrictNullable<HTMLElement>;
let searchTermNode: IStrictNullable<HTMLElement>;
let searchTerm = '';
let modelType: (typeof MODEL_TYPE)[keyof typeof MODEL_TYPE];
let cards: INullable<
// Change all "replace preview" into an icon
let extraNetworkId: `${(typeof TAB_PREFIX_LIST)[number]}_${(typeof MODEL_TYPE_LIST)[number]}_${typeof CARDID_SUFFIX}`;
let extraNetworkNode: IStrictNullable<HTMLElement>;
let metadataButton: IStrictNullable<HTMLElement>;
let additionalNode: HTMLElement;
let replacePreviewButton: IStrictNullable<HTMLElement>;
let ulNode: IStrictNullable<HTMLElement>;
let searchTermNode: IStrictNullable<HTMLElement>;
let searchTerm = '';
let modelType: (typeof MODEL_TYPE)[keyof typeof MODEL_TYPE];
let cards: INullable<
NodeListOf<
HTMLElement & {
dataset: {
@ -82,203 +84,203 @@ const updateCardForCivitai = () => {
}
>
>;
let needToAddButtons = false;
let isThumbMode = false;
let needToAddButtons = false;
let isThumbMode = false;
const modelTypeHasCards: (typeof MODEL_TYPE_LIST)[number][] = [];
const modelTypeHasCards: (typeof MODEL_TYPE_LIST)[number][] = [];
// Get current tab
for (const activeTabType of TAB_PREFIX_LIST) {
for (const jsModelType of MODEL_TYPE_LIST) {
modelType = MODEL_TYPE[jsModelType];
// Get model_type for python side
// Get current tab
for (const activeTabType of TAB_PREFIX_LIST) {
for (const jsModelType of MODEL_TYPE_LIST) {
modelType = MODEL_TYPE[jsModelType];
// Get model_type for python side
extraNetworkId = `${activeTabType}_${jsModelType}_${CARDID_SUFFIX}`;
extraNetworkNode = document.querySelector(`#${extraNetworkId}` as const);
extraNetworkId = `${activeTabType}_${jsModelType}_${CARDID_SUFFIX}`;
extraNetworkNode = document.querySelector(`#${extraNetworkId}` as const);
// Check if extra network node exists
if (is_nullable(extraNetworkNode)) continue;
// Check if extra network node exists
if (is_nullable(extraNetworkNode)) continue;
// Check if extr network is under thumbnail mode
isThumbMode = extraNetworkNode.classList.contains('extra-network-thumbs');
// Check if extr network is under thumbnail mode
isThumbMode = extraNetworkNode.classList.contains('extra-network-thumbs');
// Get all card nodes
cards = extraNetworkNode.querySelectorAll('.card');
const pending = !!document.querySelector(`#${extraNetworkId}_html .pending`);
if (!cards?.length || pending) {
if (!pending && extraNetworkNode.querySelector('.nocards')) {
modelTypeHasCards.push(jsModelType);
}
// Get all card nodes
cards = extraNetworkNode.querySelectorAll('.card');
const pending = !!document.querySelector(`#${extraNetworkId}_html .pending`);
if (!cards?.length || pending) {
if (!pending && extraNetworkNode.querySelector('.nocards')) {
modelTypeHasCards.push(jsModelType);
}
continue;
}
modelTypeHasCards.push(jsModelType);
for (const card of cards) {
if (card.dataset[DOM_CACHE_KEY] === DOM_CACHE_VALUE) break;
card.dataset[DOM_CACHE_KEY] = DOM_CACHE_VALUE;
if (card.querySelectorAll('.actions .additional a').length > 2) continue;
// Metadata_buttoncard
metadataButton = card.querySelector('.metadata-button');
// Additional node
additionalNode = card.querySelector('.actions .additional')!;
// Get ul node, which is the parent of all buttons
ulNode = card.querySelector('.actions .additional ul');
if (is_nullable(ulNode)) {
ulNode = document.createElement('ul');
additionalNode.append(ulNode);
}
// Replace preview text button
replacePreviewButton = card.querySelector('.actions .additional a');
if (is_nullable(replacePreviewButton)) {
replacePreviewButton = document.createElement('a');
additionalNode.append(replacePreviewButton);
}
// Remove br tag
ulNode.querySelector('br')?.remove();
// Check thumb mode
if (isThumbMode && additionalNode) {
additionalNode.style.display = undefined as any as string;
if (chShowButtonOnThumb) {
ulNode.style.background = BTN_THUMB_BACKGROUND;
} else {
// Reset
ulNode.style.background = undefined as any as string;
// Remove existed buttons
// Find all .a child nodes
const atags = ulNode.querySelectorAll('a');
if (!atags?.length) continue;
for (const atag of atags) {
// Reset display
atag.style.display = undefined as any;
// Remove extension's button
if (CH_BTN_TXTS.has(atag.innerHTML)) {
// Need to remove
atag.remove();
} else {
// Do not remove, just reset
atag.innerHTML = replacePreviewText;
atag.style.display = undefined as any;
atag.style.fontSize = undefined as any;
atag.style.position = undefined as any;
atag.style.backgroundImage = undefined as any;
}
continue;
}
// Just reset and remove nodes, do nothing else
continue;
}
} else {
// Full preview mode
additionalNode.style.display = chAlwaysDisplay ? 'block' : (undefined as any as string);
modelTypeHasCards.push(jsModelType);
for (const card of cards) {
if (card.dataset[DOM_CACHE_KEY] === DOM_CACHE_VALUE) break;
card.dataset[DOM_CACHE_KEY] = DOM_CACHE_VALUE;
if (card.querySelectorAll('.actions .additional a').length > 2) continue;
// Metadata_buttoncard
metadataButton = card.querySelector('.metadata-button');
// Additional node
additionalNode = card.querySelector('.actions .additional')!;
// Get ul node, which is the parent of all buttons
ulNode = card.querySelector('.actions .additional ul');
if (is_nullable(ulNode)) {
ulNode = document.createElement('ul');
additionalNode.append(ulNode);
}
// Replace preview text button
replacePreviewButton = card.querySelector('.actions .additional a');
if (is_nullable(replacePreviewButton)) {
replacePreviewButton = document.createElement('a');
additionalNode.append(replacePreviewButton);
}
// Remove br tag
ulNode.querySelector('br')?.remove();
// Check thumb mode
if (isThumbMode && additionalNode) {
additionalNode.style.display = undefined as any as string;
if (chShowButtonOnThumb) {
ulNode.style.background = BTN_THUMB_BACKGROUND;
} else {
// Reset
ulNode.style.background = undefined as any as string;
// Remove existed buttons
// Find all .a child nodes
const atags = ulNode.querySelectorAll('a');
if (!atags?.length) continue;
for (const atag of atags) {
// Reset display
atag.style.display = undefined as any;
// Remove extension's button
if (CH_BTN_TXTS.has(atag.innerHTML)) {
// Need to remove
atag.remove();
} else {
// Do not remove, just reset
atag.innerHTML = replacePreviewText;
atag.style.display = undefined as any;
atag.style.fontSize = undefined as any;
atag.style.position = undefined as any;
atag.style.backgroundImage = undefined as any;
}
}
// Just reset and remove nodes, do nothing else
continue;
}
} else {
// Full preview mode
additionalNode.style.display = chAlwaysDisplay ? 'block' : (undefined as any as string);
}
// Change replace preview text button into icon
if (replacePreviewButton.innerHTML !== '🖼️') {
needToAddButtons = true;
replacePreviewButton.innerHTML = '🖼️';
styleButton(replacePreviewButton, isThumbMode);
}
if (!needToAddButtons) continue;
// Search_term node
// Search_term = subfolder path + model name + ext
searchTermNode = card.querySelector('.actions .additional .search_term');
if (!searchTermNode) return;
// Get search_term
searchTerm = searchTermNode.innerHTML;
if (!searchTerm) continue;
// Then we need to add 3 buttons to each ul node:
const openUrlNode = document.createElement('a');
openUrlNode.href = '#';
openUrlNode.innerHTML = '🌐';
styleButton(openUrlNode, isThumbMode);
openUrlNode.title = "Open this model's civitai url";
openUrlNode.setAttribute(
'onclick',
`open_model_url(event, '${modelType}', '${searchTerm}')`,
);
const addTriggerWordsNode = document.createElement('a');
addTriggerWordsNode.href = '#';
addTriggerWordsNode.innerHTML = '💡';
styleButton(addTriggerWordsNode, isThumbMode);
addTriggerWordsNode.title = 'Add trigger words to prompt';
addTriggerWordsNode.setAttribute(
'onclick',
`add_trigger_words(event, '${modelType}', '${searchTerm}')`,
);
const usePreviewPromptNode = document.createElement('a');
usePreviewPromptNode.href = '#';
usePreviewPromptNode.innerHTML = '🏷️';
styleButton(usePreviewPromptNode, isThumbMode);
usePreviewPromptNode.title = 'Use prompt from preview image';
usePreviewPromptNode.setAttribute(
'onclick',
`use_preview_prompt(event, '${modelType}', '${searchTerm}')`,
);
// Add to card
ulNode.append(openUrlNode);
// Add br if metadata_button exists
if (isThumbMode && metadataButton) ulNode.append(document.createElement('br'));
ulNode.append(addTriggerWordsNode);
ulNode.append(usePreviewPromptNode);
}
}
// Change replace preview text button into icon
if (replacePreviewButton.innerHTML !== '🖼️') {
needToAddButtons = true;
replacePreviewButton.innerHTML = '🖼️';
styleButton(replacePreviewButton, isThumbMode);
}
if (!needToAddButtons) continue;
// Search_term node
// Search_term = subfolder path + model name + ext
searchTermNode = card.querySelector('.actions .additional .search_term');
if (!searchTermNode) return;
// Get search_term
searchTerm = searchTermNode.innerHTML;
if (!searchTerm) continue;
// Then we need to add 3 buttons to each ul node:
const openUrlNode = document.createElement('a');
openUrlNode.href = '#';
openUrlNode.innerHTML = '🌐';
styleButton(openUrlNode, isThumbMode);
openUrlNode.title = "Open this model's civitai url";
openUrlNode.setAttribute(
'onclick',
`open_model_url(event, '${modelType}', '${searchTerm}')`,
);
const addTriggerWordsNode = document.createElement('a');
addTriggerWordsNode.href = '#';
addTriggerWordsNode.innerHTML = '💡';
styleButton(addTriggerWordsNode, isThumbMode);
addTriggerWordsNode.title = 'Add trigger words to prompt';
addTriggerWordsNode.setAttribute(
'onclick',
`add_trigger_words(event, '${modelType}', '${searchTerm}')`,
);
const usePreviewPromptNode = document.createElement('a');
usePreviewPromptNode.href = '#';
usePreviewPromptNode.innerHTML = '🏷️';
styleButton(usePreviewPromptNode, isThumbMode);
usePreviewPromptNode.title = 'Use prompt from preview image';
usePreviewPromptNode.setAttribute(
'onclick',
`use_preview_prompt(event, '${modelType}', '${searchTerm}')`,
);
// Add to card
ulNode.append(openUrlNode);
// Add br if metadata_button exists
if (isThumbMode && metadataButton) ulNode.append(document.createElement('br'));
ulNode.append(addTriggerWordsNode);
ulNode.append(usePreviewPromptNode);
}
}
}
return modelTypeHasCards;
return modelTypeHasCards;
};
export default () => {
let checkDomCurrent: INullable<HTMLElement>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let x: number = 0;
let fn: () => any;
// eslint-disable-next-line unicorn/consistent-function-scoping
const fnClick = () => {
let checkDomCurrent: INullable<HTMLElement>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let x: number = 0;
let fn: () => any;
// eslint-disable-next-line unicorn/consistent-function-scoping
const fnClick = () => {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
setTimeout(fn, 2000);
};
fn = () => {
let retryTimes = 0;
const fixInterval = setInterval(() => {
console.debug('🤯 [civitai helper] update card for civitai');
const checkDom = document.querySelector('#txt2img_lora_cards') as any;
if (checkDom || retryTimes > 10) {
if (checkDomCurrent !== checkDom) {
x = 0;
checkDomCurrent = checkDom;
for (const activeTabType of TAB_PREFIX_LIST) {
const elems = document.querySelectorAll(`#${activeTabType}_extra_tabs .tab-nav button`);
if (elems) {
for (const elem of elems) {
elem.removeEventListener('click', fnClick);
elem.addEventListener('click', fnClick);
}
setTimeout(fn, 2000);
};
fn = () => {
let retryTimes = 0;
const fixInterval = setInterval(() => {
consola.info('🤯 [civitai helper] update card for civitai');
const checkDom = document.querySelector('#txt2img_lora_cards') as any;
if (checkDom || retryTimes > 10) {
if (checkDomCurrent !== checkDom) {
x = 0;
checkDomCurrent = checkDom;
for (const activeTabType of TAB_PREFIX_LIST) {
const elems = document.querySelectorAll(`#${activeTabType}_extra_tabs .tab-nav button`);
if (elems) {
for (const elem of elems) {
elem.removeEventListener('click', fnClick);
elem.addEventListener('click', fnClick);
}
}
}
}
const y = updateCardForCivitai()?.length as number;
if (typeof y === 'number' && y < x) x = y;
if (retryTimes > 10 || !checkDom || y >= MODEL_TYPE_LIST.length || y > x) {
clearInterval(fixInterval);
x = y ?? x;
}
}
}
}
const y = updateCardForCivitai()?.length as number;
if (typeof y === 'number' && y < x) x = y;
if (retryTimes > 10 || !checkDom || y >= MODEL_TYPE_LIST.length || y > x) {
clearInterval(fixInterval);
x = y ?? x;
}
}
retryTimes++;
}, 2000);
};
retryTimes++;
}, 2000);
};
return fn();
return fn();
};

View File

@ -1,80 +1,85 @@
import { consola } from 'consola';
const MIN_WIDTH = 240;
const addDraggable = (tabId: string) => {
const settings = document.querySelector(`#${tabId}_settings`) as HTMLDivElement;
const checkDraggableLine = document.querySelector(
`#tab_${tabId} .draggable-line`,
) as HTMLDivElement;
if (!settings || checkDraggableLine) return;
const settings = document.querySelector(`#${tabId}_settings`) as HTMLDivElement;
const checkDraggableLine = document.querySelector(
`#tab_${tabId} .draggable-line`,
) as HTMLDivElement;
if (!settings || checkDraggableLine) return;
settings.style.minWidth = `min(${MIN_WIDTH}px, 100%)`;
settings.style.minWidth = `min(${MIN_WIDTH}px, 100%)`;
const lineWrapper = document.createElement('div');
lineWrapper.classList.add('draggable-line');
const lineWrapper = document.createElement('div');
lineWrapper.classList.add('draggable-line');
settings.after(lineWrapper);
settings.after(lineWrapper);
const container: HTMLElement | any = settings.parentElement;
container.classList.add('draggable-container');
const container: HTMLElement | any = settings.parentElement;
container.classList.add('draggable-container');
let results: HTMLDivElement = document.querySelector(`#${tabId}_results`) as HTMLDivElement;
let results: HTMLDivElement = document.querySelector(`#${tabId}_results`) as HTMLDivElement;
if (!results) return;
if (!results) return;
if (tabId === 'extras') results = results.parentElement as HTMLDivElement;
if (tabId === 'extras') results = results.parentElement as HTMLDivElement;
results.style.minWidth = `${MIN_WIDTH}px`;
let linePosition = 50;
settings.style.flexBasis = `${linePosition}%`;
results.style.flexBasis = `${100 - linePosition}%`;
let isDragging = false;
lineWrapper.addEventListener('mousedown', (e) => {
isDragging = true;
e.preventDefault();
});
document.addEventListener('mousemove', (event) => {
if (!isDragging) return;
const tab = document.querySelector(`#tab_${tabId}`) as HTMLDivElement;
if (!tab) return;
let offsetX = tab.offsetLeft;
let parent = tab.offsetParent as HTMLDivElement;
while (parent) {
offsetX += parent.offsetLeft;
parent = parent.offsetParent as HTMLDivElement;
}
const containerWidth = container.offsetWidth;
const mouseX = event.clientX;
const linePosition = ((mouseX - offsetX) / containerWidth) * 100;
if (linePosition <= (MIN_WIDTH / containerWidth) * 100) return;
if (linePosition >= (1 - MIN_WIDTH / containerWidth) * 100) return;
results.style.minWidth = `${MIN_WIDTH}px`;
let linePosition = 50;
settings.style.flexBasis = `${linePosition}%`;
results.style.flexBasis = `${100 - linePosition}%`;
});
document.addEventListener('mouseup', () => {
isDragging = false;
});
let isDragging = false;
lineWrapper.addEventListener('mousedown', (e) => {
isDragging = true;
e.preventDefault();
});
document.addEventListener('mousemove', (event) => {
if (!isDragging) return;
const tab = document.querySelector(`#tab_${tabId}`) as HTMLDivElement;
if (!tab) return;
let offsetX = tab.offsetLeft;
let parent = tab.offsetParent as HTMLDivElement;
while (parent) {
offsetX += parent.offsetLeft;
parent = parent.offsetParent as HTMLDivElement;
}
const containerWidth = container.offsetWidth;
const mouseX = event.clientX;
const linePosition = ((mouseX - offsetX) / containerWidth) * 100;
if (linePosition <= (MIN_WIDTH / containerWidth) * 100) return;
if (linePosition >= (1 - MIN_WIDTH / containerWidth) * 100) return;
settings.style.flexBasis = `${linePosition}%`;
results.style.flexBasis = `${100 - linePosition}%`;
});
document.addEventListener('mouseup', () => {
isDragging = false;
});
};
export default () => {
console.time('🤯 [layout] inject - DraggablePanel');
addDraggable('txt2img');
addDraggable('img2img');
const extrasSetting = document.querySelector('#extras_results')?.parentElement
?.previousElementSibling as HTMLDivElement;
if (extrasSetting) {
extrasSetting.id = 'extras_settings';
addDraggable('extras');
}
console.timeEnd('🤯 [layout] inject - DraggablePanel');
try {
addDraggable('txt2img');
addDraggable('img2img');
const extrasSetting = document.querySelector('#extras_results')?.parentElement
?.previousElementSibling as HTMLDivElement;
if (extrasSetting) {
extrasSetting.id = 'extras_settings';
addDraggable('extras');
}
consola.success('🤯 [layout] inject - DraggablePanel');
} catch (error) {
consola.error('🤯 [layout] inject - DraggablePanel', error);
}
};

View File

@ -1,283 +1,284 @@
import { consola } from 'consola';
/**
*
*/
export const Converter = {
/**
/**
*
* @param type -
*/
addPromptButton(type: string): void {
console.debug('🤯 [formatPrompt] inject', type);
const actionsColumn: HTMLElement | null = gradioApp().querySelector(
`#${type}_tools > div.form`,
);
const formatBtn: HTMLElement | null = gradioApp().querySelector(`#${type}_formatconvert`);
if (!actionsColumn || formatBtn) return;
const convertButton: HTMLElement = Converter.createButton(`${type}_formatconvert`, '🪄', () =>
Converter.onClickConvert(type));
actionsColumn.append(convertButton);
},
addPromptButton(type: string): void {
consola.info('🤯 [formatPrompt] inject', type);
const actionsColumn: HTMLElement | null = gradioApp().querySelector(
`#${type}_tools > div.form`,
);
const formatBtn: HTMLElement | null = gradioApp().querySelector(`#${type}_formatconvert`);
if (!actionsColumn || formatBtn) return;
const convertButton: HTMLElement = Converter.createButton(`${type}_formatconvert`, '🪄', () =>
Converter.onClickConvert(type));
actionsColumn.append(convertButton);
},
/**
/**
*
* @param input
* @returns
*/
convert(input: string): string {
const re_attention = /\{|\[|\}|\]|[^{}[\]]+/gmu;
convert(input: string): string {
const re_attention = /\{|\[|\}|\]|[^{}[\]]+/gmu;
let text = Converter.convertStr(input);
const textArray = Converter.convertStr2Array(text);
text = Converter.convertArray2Str(textArray);
let text = Converter.convertStr(input);
const textArray = Converter.convertStr2Array(text);
text = Converter.convertArray2Str(textArray);
let res: [string, number][] = [];
let res: [string, number][] = [];
const curly_bracket_multiplier = 1.05;
const square_bracket_multiplier = 1 / 1.05;
const curly_bracket_multiplier = 1.05;
const square_bracket_multiplier = 1 / 1.05;
const brackets: Record<string, { multiplier: number; stack: number[] }> = {
'[': { multiplier: square_bracket_multiplier, stack: [] },
'{': { multiplier: curly_bracket_multiplier, stack: [] },
};
const brackets: Record<string, { multiplier: number; stack: number[] }> = {
'[': { multiplier: square_bracket_multiplier, stack: [] },
'{': { multiplier: curly_bracket_multiplier, stack: [] },
};
/**
/**
*
* @param start_position
* @param multiplier
*/
function multiply_range(start_position: number, multiplier: number) {
for (let pos = start_position; pos < res.length; pos++) {
res[pos][1] = Converter.round(res[pos][1] * multiplier);
}
}
for (const match of text.matchAll(re_attention)) {
let word = match[0];
if (word in brackets) {
brackets[word].stack.push(res.length);
} else if (word === '}' || word === ']') {
const bracket = brackets[word === '}' ? '{' : '['];
if (bracket.stack.length > 0) {
multiply_range(bracket.stack.pop()!, bracket.multiplier);
function multiply_range(start_position: number, multiplier: number) {
for (let pos = start_position; pos < res.length; pos++) {
res[pos][1] = Converter.round(res[pos][1] * multiplier);
}
}
} else {
res.push([word, 1]);
}
}
for (const bracketType of Object.keys(brackets)) {
for (const pos of brackets[bracketType].stack) {
multiply_range(pos, brackets[bracketType].multiplier);
}
}
for (const match of text.matchAll(re_attention)) {
let word = match[0];
if (res.length === 0) {
res = [['', 1]];
}
if (word in brackets) {
brackets[word].stack.push(res.length);
} else if (word === '}' || word === ']') {
const bracket = brackets[word === '}' ? '{' : '['];
if (bracket.stack.length > 0) {
multiply_range(bracket.stack.pop()!, bracket.multiplier);
}
} else {
res.push([word, 1]);
}
}
let index = 0;
while (index + 1 < res.length) {
if (res[index][1] === res[index + 1][1]) {
res[index][0] += res[index + 1][0];
res.splice(index + 1, 1);
} else {
index += 1;
}
}
for (const bracketType of Object.keys(brackets)) {
for (const pos of brackets[bracketType].stack) {
multiply_range(pos, brackets[bracketType].multiplier);
}
}
let result = '';
for (const [word, value] of res) {
result += value === 1 ? word : `(${word}:${value.toString()})`;
}
return result;
},
if (res.length === 0) {
res = [['', 1]];
}
/**
let index = 0;
while (index + 1 < res.length) {
if (res[index][1] === res[index + 1][1]) {
res[index][0] += res[index + 1][0];
res.splice(index + 1, 1);
} else {
index += 1;
}
}
let result = '';
for (const [word, value] of res) {
result += value === 1 ? word : `(${word}:${value.toString()})`;
}
return result;
},
/**
*
* @param array
* @returns
*/
convertArray2Str(array: string[]): string {
const newArray = array.map((item) => {
if (item.includes('<')) return item;
const newItem = item
.replaceAll(/\s+/g, ' ')
.replaceAll(/|\.\|。/g, ',')
.replaceAll(/“||”|"|\/'/g, '')
.replaceAll(', ', ',')
.replaceAll(',,', ',')
.replaceAll(',', ', ');
return Converter.convertStr2Array(newItem).join(', ');
});
return newArray.join(', ');
},
convertArray2Str(array: string[]): string {
const newArray = array.map((item) => {
if (item.includes('<')) return item;
const newItem = item
.replaceAll(/\s+/g, ' ')
.replaceAll(/|\.\|。/g, ',')
.replaceAll(/“||”|"|\/'/g, '')
.replaceAll(', ', ',')
.replaceAll(',,', ',')
.replaceAll(',', ', ');
return Converter.convertStr2Array(newItem).join(', ');
});
return newArray.join(', ');
},
/**
/**
*
* @param srt
* @returns
*/
convertStr(srt: string): string {
return srt.replaceAll('', ':').replaceAll('', '(').replaceAll('', ')');
},
convertStr(srt: string): string {
return srt.replaceAll('', ':').replaceAll('', '(').replaceAll('', ')');
},
/**
/**
*
* @param str
* @returns
*/
convertStr2Array(string_: string): string[] {
convertStr2Array(string_: string): string[] {
// 匹配各种括号中的内容,包括括号本身
const bracketRegex = /([()<>[\]])/g;
const bracketRegex = /([()<>[\]])/g;
/**
/**
*
* @param str
* @returns
*/
const splitByBracket = (string__: string): string[] => {
const array: string[] = [];
let start = 0;
let depth = 0;
let match;
while ((match = bracketRegex.exec(string__)) !== null) {
if (depth === 0 && match.index > start) {
array.push(string__.slice(start, match.index));
start = match.index;
}
if (match[0] === '(' || match[0] === '<' || match[0] === '[') {
depth++;
} else if (match[0] === ')' || match[0] === '>' || match[0] === ']') {
depth--;
}
if (depth === 0) {
array.push(string__.slice(start, match.index + 1));
start = match.index + 1;
}
}
if (start < string__.length) {
array.push(string__.slice(Math.max(0, start)));
}
return array;
};
const splitByBracket = (string__: string): string[] => {
const array: string[] = [];
let start = 0;
let depth = 0;
let match;
while ((match = bracketRegex.exec(string__)) !== null) {
if (depth === 0 && match.index > start) {
array.push(string__.slice(start, match.index));
start = match.index;
}
if (match[0] === '(' || match[0] === '<' || match[0] === '[') {
depth++;
} else if (match[0] === ')' || match[0] === '>' || match[0] === ']') {
depth--;
}
if (depth === 0) {
array.push(string__.slice(start, match.index + 1));
start = match.index + 1;
}
}
if (start < string__.length) {
array.push(string__.slice(Math.max(0, start)));
}
return array;
};
/**
/**
*
* @param str
* @returns
*/
const splitByComma = (string__: string): string[] => {
const array: string[] = [];
let start = 0;
let inBracket = false;
for (let index = 0; index < string__.length; index++) {
if (string__[index] === ',' && !inBracket) {
array.push(string__.slice(start, index).trim());
start = index + 1;
} else if (bracketRegex.test(string__[index])) {
inBracket = !inBracket;
}
}
array.push(string__.slice(Math.max(0, start)).trim());
return array;
};
const splitByComma = (string__: string): string[] => {
const array: string[] = [];
let start = 0;
let inBracket = false;
for (let index = 0; index < string__.length; index++) {
if (string__[index] === ',' && !inBracket) {
array.push(string__.slice(start, index).trim());
start = index + 1;
} else if (bracketRegex.test(string__[index])) {
inBracket = !inBracket;
}
}
array.push(string__.slice(Math.max(0, start)).trim());
return array;
};
/**
/**
*
* @param str
* @returns
*/
const cleanString = (string__: string): string[] => {
let array = splitByBracket(string__);
array = array.flatMap((s) => splitByComma(s));
return array.filter((s) => s !== '');
};
const cleanString = (string__: string): string[] => {
let array = splitByBracket(string__);
array = array.flatMap((s) => splitByComma(s));
return array.filter((s) => s !== '');
};
return cleanString(string_)
.filter((item) => {
const pattern = /^[\s,]+$/;
return !pattern.test(item);
})
.filter(Boolean)
.sort((a, b) => {
return a.includes('<') && !b.includes('<') ?
1 :
b.includes('<') && !a.includes('<') ?
-1 :
0;
});
},
return cleanString(string_)
.filter((item) => {
const pattern = /^[\s,]+$/;
return !pattern.test(item);
})
.filter(Boolean)
.sort((a, b) => {
return a.includes('<') && !b.includes('<') ?
1 :
b.includes('<') && !a.includes('<') ?
-1 :
0;
});
},
/**
/**
*
* @param id id
* @param innerHTML
* @param onClick
* @returns
*/
createButton(id: string, innerHTML: string, onClick: () => void): HTMLButtonElement {
const button = document.createElement('button');
button.id = id;
button.type = 'button';
button.innerHTML = innerHTML;
button.title = 'Format prompt~🪄';
button.className = 'lg secondary gradio-button tool svelte-cmf5ev';
button.addEventListener('click', onClick);
return button;
},
createButton(id: string, innerHTML: string, onClick: () => void): HTMLButtonElement {
const button = document.createElement('button');
button.id = id;
button.type = 'button';
button.innerHTML = innerHTML;
button.title = 'Format prompt~🪄';
button.className = 'lg secondary gradio-button tool svelte-cmf5ev';
button.addEventListener('click', onClick);
return button;
},
/**
/**
* input
* @param target
*/
dispatchInputEvent(target: EventTarget) {
let inputEvent = new Event('input');
Object.defineProperty(inputEvent, 'target', { value: target });
target.dispatchEvent(inputEvent);
},
dispatchInputEvent(target: EventTarget) {
let inputEvent = new Event('input');
Object.defineProperty(inputEvent, 'target', { value: target });
target.dispatchEvent(inputEvent);
},
/**
/**
*
* @param type
*/
onClickConvert(type: string) {
const default_prompt = '';
const default_negative = '';
onClickConvert(type: string) {
const default_prompt = '';
const default_negative = '';
const prompt = gradioApp().querySelector(
`#${type}_prompt > label > textarea`,
) as HTMLTextAreaElement;
const result = Converter.convert(prompt.value);
prompt.value =
const prompt = gradioApp().querySelector(
`#${type}_prompt > label > textarea`,
) as HTMLTextAreaElement;
const result = Converter.convert(prompt.value);
prompt.value =
result.match(/^masterpiece, best quality,/) === null ? default_prompt + result : result;
Converter.dispatchInputEvent(prompt);
const negprompt = gradioApp().querySelector(
`#${type}_neg_prompt > label > textarea`,
) as HTMLTextAreaElement;
const negResult = Converter.convert(negprompt.value);
negprompt.value =
Converter.dispatchInputEvent(prompt);
const negprompt = gradioApp().querySelector(
`#${type}_neg_prompt > label > textarea`,
) as HTMLTextAreaElement;
const negResult = Converter.convert(negprompt.value);
negprompt.value =
negResult.match(/^lowres,/) === null ?
negResult.length === 0 ?
default_negative :
default_negative + negResult :
negResult;
Converter.dispatchInputEvent(negprompt);
},
negResult.length === 0 ?
default_negative :
default_negative + negResult :
negResult;
Converter.dispatchInputEvent(negprompt);
},
/**
/**
*
* @param value
* @returns
*/
round(value: number): number {
return Math.round(value * 10_000) / 10_000;
},
round(value: number): number {
return Math.round(value * 10_000) / 10_000;
},
};
export default () => {
console.time('🤯 [formatPrompt] inject');
Converter.addPromptButton('txt2img');
Converter.addPromptButton('img2img');
console.timeEnd('🤯 [formatPrompt] inject');
Converter.addPromptButton('txt2img');
Converter.addPromptButton('img2img');
consola.success('🤯 [formatPrompt] inject');
};

View File

@ -1,95 +1,95 @@
import { consola } from 'consola';
import {
archiveRestore,
arrowDown,
arrowDownLeft,
arrowDownWideNarrow,
arrowLeft,
arrowRight,
arrowRightLeft,
arrowUpDown,
book,
box,
brush,
clipboardList,
cornerRightUp,
dices,
download,
fileArchive,
folderClosed,
grid2x2,
image,
laptop2,
maximize,
panelRight,
paperclip,
penSquare,
pencilRuler,
play,
refreshCcw,
save,
settings,
trash,
undo,
wand2,
webcam,
x, // @ts-ignore
archiveRestore,
arrowDown,
arrowDownLeft,
arrowDownWideNarrow,
arrowLeft,
arrowRight,
arrowRightLeft,
arrowUpDown,
book,
box,
brush,
clipboardList,
cornerRightUp,
dices,
download,
fileArchive,
folderClosed,
grid2x2,
image,
laptop2,
maximize,
panelRight,
paperclip,
penSquare,
pencilRuler,
play,
refreshCcw,
save,
settings,
trash,
undo,
wand2,
webcam,
x, // @ts-ignore
} from 'lucide-static';
const replaceIcon = (element: HTMLElement, emoji: string[], svg: string, size: number) => {
if (!element?.textContent || !svg) return;
for (const e of emoji) {
if (element?.textContent?.includes(e)) {
element.innerHTML = svg
.replace(`width="24"`, `width="${size}"`)
.replace(`height="24"`, `height="${size}"`);
if (!element?.textContent || !svg) return;
for (const e of emoji) {
if (element?.textContent?.includes(e)) {
element.innerHTML = svg
.replace(`width="24"`, `width="${size}"`)
.replace(`height="24"`, `height="${size}"`);
}
}
}
};
export default () => {
console.time('🤯 [svgIcon] replace');
for (const button of document.querySelectorAll('button')) {
replaceIcon(button, ['🖌️'], penSquare, 16);
replaceIcon(button, ['🗃️'], fileArchive, 16);
replaceIcon(button, ['🖼️'], image, 16);
replaceIcon(button, ['🎨️'], brush, 16);
replaceIcon(button, ['📂'], folderClosed, 16);
replaceIcon(button, ['🔄', '🔁', '♻️'], refreshCcw, 16);
replaceIcon(button, ['↙️'], arrowDownLeft, 16);
replaceIcon(button, ['⤴'], cornerRightUp, 16);
replaceIcon(button, ['↕️'], arrowDownWideNarrow, 16);
replaceIcon(button, ['🗑️'], trash, 16);
replaceIcon(button, ['📋'], clipboardList, 16);
replaceIcon(button, ['💾'], save, 16);
replaceIcon(button, ['🎲️'], dices, 16);
replaceIcon(button, ['🪄'], wand2, 16);
replaceIcon(button, ['⚙️'], settings, 16);
replaceIcon(button, ['➡️'], arrowRight, 16);
replaceIcon(button, ['⇅'], arrowUpDown, 16);
replaceIcon(button, ['⇄'], arrowRightLeft, 16);
replaceIcon(button, ['🎴'], panelRight, 16);
replaceIcon(button, ['🌀'], archiveRestore, 16);
replaceIcon(button, ['💥'], play, 16);
replaceIcon(button, ['📷'], webcam, 16);
replaceIcon(button, ['📝'], laptop2, 16);
replaceIcon(button, ['📐'], pencilRuler, 16);
replaceIcon(button, ['⬇️'], arrowDown, 16);
replaceIcon(button, ['↩'], undo, 16);
replaceIcon(button, ['📒'], book, 16);
replaceIcon(button, ['📎'], paperclip, 16);
replaceIcon(button, ['📦'], box, 16);
}
for (const button of document.querySelectorAll('button')) {
replaceIcon(button, ['🖌️'], penSquare, 16);
replaceIcon(button, ['🗃️'], fileArchive, 16);
replaceIcon(button, ['🖼️'], image, 16);
replaceIcon(button, ['🎨️'], brush, 16);
replaceIcon(button, ['📂'], folderClosed, 16);
replaceIcon(button, ['🔄', '🔁', '♻️'], refreshCcw, 16);
replaceIcon(button, ['↙️'], arrowDownLeft, 16);
replaceIcon(button, ['⤴'], cornerRightUp, 16);
replaceIcon(button, ['↕️'], arrowDownWideNarrow, 16);
replaceIcon(button, ['🗑️'], trash, 16);
replaceIcon(button, ['📋'], clipboardList, 16);
replaceIcon(button, ['💾'], save, 16);
replaceIcon(button, ['🎲️'], dices, 16);
replaceIcon(button, ['🪄'], wand2, 16);
replaceIcon(button, ['⚙️'], settings, 16);
replaceIcon(button, ['➡️'], arrowRight, 16);
replaceIcon(button, ['⇅'], arrowUpDown, 16);
replaceIcon(button, ['⇄'], arrowRightLeft, 16);
replaceIcon(button, ['🎴'], panelRight, 16);
replaceIcon(button, ['🌀'], archiveRestore, 16);
replaceIcon(button, ['💥'], play, 16);
replaceIcon(button, ['📷'], webcam, 16);
replaceIcon(button, ['📝'], laptop2, 16);
replaceIcon(button, ['📐'], pencilRuler, 16);
replaceIcon(button, ['⬇️'], arrowDown, 16);
replaceIcon(button, ['↩'], undo, 16);
replaceIcon(button, ['📒'], book, 16);
replaceIcon(button, ['📎'], paperclip, 16);
replaceIcon(button, ['📦'], box, 16);
}
for (const span of document.querySelectorAll('span')) {
replaceIcon(span, ['⤡'], maximize, 36);
replaceIcon(span, ['⊞'], grid2x2, 36);
replaceIcon(span, ['🖫'], download, 36);
replaceIcon(span, ['×'], x, 36);
}
for (const span of document.querySelectorAll('span')) {
replaceIcon(span, ['⤡'], maximize, 36);
replaceIcon(span, ['⊞'], grid2x2, 36);
replaceIcon(span, ['🖫'], download, 36);
replaceIcon(span, ['×'], x, 36);
}
for (const a of document.querySelectorAll('a')) {
replaceIcon(a, [''], arrowLeft, 36);
replaceIcon(a, [''], arrowRight, 36);
}
console.timeEnd('🤯 [svgIcon] replace');
for (const a of document.querySelectorAll('a')) {
replaceIcon(a, [''], arrowLeft, 36);
replaceIcon(a, [''], arrowRight, 36);
}
consola.success('🤯 [svgIcon] replace');
};

View File

@ -1,3 +1,4 @@
import { consola } from 'consola';
import type { StateCreator } from 'zustand/vanilla';
import { getLatestVersion, getLocaleOptions, getSetting, getVersion, postSetting } from './api';
@ -18,83 +19,82 @@ export interface StoreAction {
}
export const createSettings: StateCreator<Store, [['zustand/devtools', never]], [], StoreAction> = (
set,
get,
set,
get,
) => ({
onInit: async() => {
set(() => ({ loading: true }), false, 'onInit');
const { onLoadSetting, onLoadVersion, onLoadLatestVersion, onLoadLocalOptions } = get();
await onLoadLocalOptions();
await onLoadVersion();
await onLoadLatestVersion();
await onLoadSetting();
set(() => ({ loading: false }), false, 'onInit');
},
onLoadLatestVersion: async() => {
const latestVersion = await getLatestVersion();
set(() => ({ latestVersion }), false, 'onLoadLatestVersion');
},
onLoadLocalOptions: async() => {
const localeOptions = await getLocaleOptions();
set(() => ({ localeOptions }), false, 'onLoadLocalOptions');
},
onLoadSetting: async() => {
console.time('🤯 [setting] loaded');
let themeSetting;
const webuiSetting: any = await getSetting();
onInit: async() => {
set(() => ({ loading: true }), false, 'onInit');
const { onLoadSetting, onLoadVersion, onLoadLatestVersion, onLoadLocalOptions } = get();
await onLoadLocalOptions();
await onLoadVersion();
await onLoadLatestVersion();
await onLoadSetting();
set(() => ({ loading: false }), false, 'onInit');
},
onLoadLatestVersion: async() => {
const latestVersion = await getLatestVersion();
set(() => ({ latestVersion }), false, 'onLoadLatestVersion');
},
onLoadLocalOptions: async() => {
const localeOptions = await getLocaleOptions();
set(() => ({ localeOptions }), false, 'onLoadLocalOptions');
},
onLoadSetting: async() => {
let themeSetting;
const webuiSetting: any = await getSetting();
if (webuiSetting) {
console.info('🤯 [setting] loaded webui setting');
themeSetting = webuiSetting;
}
if (webuiSetting) {
consola.start('🤯 [setting] loaded webui setting');
themeSetting = webuiSetting;
}
if (!themeSetting) {
const localSetting: any = localStorage.getItem(SETTING_KEY);
if (localSetting) {
console.info('🤯 [setting] loaded local setting');
themeSetting = JSON.parse(localSetting);
}
}
if (!themeSetting) {
const localSetting: any = localStorage.getItem(SETTING_KEY);
if (localSetting) {
consola.info('🤯 [setting] loaded local setting');
themeSetting = JSON.parse(localSetting);
}
}
if (!themeSetting) {
const fallbackLocalSetting: any = localStorage.getItem(FALLBACK_SETTING_KEY);
if (fallbackLocalSetting) {
console.info('🤯 [setting] loaded fallback local setting');
themeSetting = JSON.parse(fallbackLocalSetting);
}
}
if (!themeSetting) {
const fallbackLocalSetting: any = localStorage.getItem(FALLBACK_SETTING_KEY);
if (fallbackLocalSetting) {
consola.info('🤯 [setting] loaded fallback local setting');
themeSetting = JSON.parse(fallbackLocalSetting);
}
}
if (!themeSetting) {
console.info('🤯 [setting] loaded default setting');
themeSetting = DEFAULT_SETTING;
}
if (!themeSetting) {
consola.info('🤯 [setting] loaded default setting');
themeSetting = DEFAULT_SETTING;
}
const setting = { ...DEFAULT_SETTING, ...themeSetting };
const setting = { ...DEFAULT_SETTING, ...themeSetting };
await postSetting(setting);
set(() => ({ setting }), false, 'onLoadSetting');
console.table(setting);
console.timeEnd('🤯 [setting] loaded');
},
onLoadVersion: async() => {
const version = await getVersion();
set(() => ({ version }), false, 'onLoadVersion');
},
onSetSetting: async(setting) => {
const oldSetting = get().setting;
const newSetting = { ...oldSetting, ...setting };
localStorage.setItem(SETTING_KEY, JSON.stringify(newSetting));
await postSetting(newSetting);
set(() => ({ setting: newSetting }), false, 'onSetSetting');
},
onSetThemeMode: (themeMode) => {
set(() => ({ themeMode }), false, 'onSetThemeMode');
},
setCurrentTab: () => {
const currentTab = get_uiCurrentTabContent()?.id;
console.debug('🤯 [tab] onChange', currentTab);
if (currentTab && currentTab !== get().currentTab) {
set({ currentTab }, false, 'setCurrentTab');
}
},
await postSetting(setting);
set(() => ({ setting }), false, 'onLoadSetting');
consola.success('🤯 [setting] loaded');
console.table(setting);
},
onLoadVersion: async() => {
const version = await getVersion();
set(() => ({ version }), false, 'onLoadVersion');
},
onSetSetting: async(setting) => {
const oldSetting = get().setting;
const newSetting = { ...oldSetting, ...setting };
localStorage.setItem(SETTING_KEY, JSON.stringify(newSetting));
await postSetting(newSetting);
set(() => ({ setting: newSetting }), false, 'onSetSetting');
},
onSetThemeMode: (themeMode) => {
set(() => ({ themeMode }), false, 'onSetThemeMode');
},
setCurrentTab: () => {
const currentTab = get_uiCurrentTabContent()?.id;
consola.info('🤯 [tab] onChange', currentTab);
if (currentTab && currentTab !== get().currentTab) {
set({ currentTab }, false, 'setCurrentTab');
}
},
});

View File

@ -9,41 +9,41 @@ import type { WebuiSetting } from './initialState';
export const DEFAULT_VERSION: string = version;
export const DEFAULT_LOCALE_OPTIONS: SelectProps['options'] = defualtLocaleOptions;
export const getSetting = async(): Promise<WebuiSetting | undefined> => {
const res = await fetch('/lobe/config');
const data = (await res.json()) as WebuiSetting;
if (!data || (data as any)?.empty) return undefined;
return data;
const res = await fetch('/lobe/config');
const data = (await res.json()) as WebuiSetting;
if (!data || (data as any)?.empty) return undefined;
return data;
};
export const postSetting = async(setting: WebuiSetting) => {
await fetch('/lobe/config', {
body: JSON.stringify(setting),
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
});
await fetch('/lobe/config', {
body: JSON.stringify(setting),
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
});
};
export const getVersion = async(): Promise<string> => {
const res = await fetch('/lobe/package');
const data = (await res.json()) as any;
if (!data || data.empty || !data.version) return DEFAULT_VERSION;
return data.version;
const res = await fetch('/lobe/package');
const data = (await res.json()) as any;
if (!data || data.empty || !data.version) return DEFAULT_VERSION;
return data.version;
};
export const getLocaleOptions = async(): Promise<SelectProps['options']> => {
const res = await fetch('/lobe/locales/options');
const data = (await res.json()) as SelectProps['options'];
if (!data || data?.length === 0) return DEFAULT_LOCALE_OPTIONS;
return data;
const res = await fetch('/lobe/locales/options');
const data = (await res.json()) as SelectProps['options'];
if (!data || data?.length === 0) return DEFAULT_LOCALE_OPTIONS;
return data;
};
export const getLatestVersion = async(): Promise<string> => {
const res = await fetch(
`https://api.github.com/repos/${homepage.replace('https://github.com/', '')}/releases/latest`,
);
const data = (await res.json()) as any;
if (!data || !data.tag_name) return DEFAULT_VERSION;
return semver.clean(data.tag_name as string) || DEFAULT_VERSION;
const res = await fetch(
`https://api.github.com/repos/${homepage.replace('https://github.com/', '')}/releases/latest`,
);
const data = (await res.json()) as any;
if (!data || !data.tag_name) return DEFAULT_VERSION;
return semver.clean(data.tag_name as string) || DEFAULT_VERSION;
};

View File

@ -1,9 +1,10 @@
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
import { shallow } from 'zustand/shallow';
import { createWithEqualityFn } from 'zustand/traditional';
import { type Store, createStore } from './store';
export const useAppStore = create<Store>()(devtools(createStore));
export const useAppStore = createWithEqualityFn<Store>()(devtools(createStore), shallow);
export * from './action';
export * from './initialState';

View File

@ -34,30 +34,30 @@ export interface WebuiSetting {
export type WebuiSettingKeys = keyof WebuiSetting;
export const DEFAULT_SETTING: WebuiSetting = {
confirmPageUnload: false,
enableExtraNetworkSidebar: true,
enableHighlight: false,
enableSidebar: true,
enableWebFont: true,
extraNetworkCardSize: 86,
extraNetworkFixedMode: 'fixed',
extraNetworkSidebarExpand: true,
extraNetworkSidebarWidth: 340,
i18n: 'en_US',
layoutHideFooter: false,
layoutSplitPreview: false,
liteAnimation: false,
logoCustomTitle: '',
logoCustomUrl: '',
logoType: 'lobe',
neutralColor: undefined,
primaryColor: undefined,
promptEditor: false,
promptTextareaType: 'resizable',
sidebarExpand: true,
sidebarFixedMode: 'fixed',
sidebarWidth: 280,
svgIcon: true,
confirmPageUnload: false,
enableExtraNetworkSidebar: true,
enableHighlight: false,
enableSidebar: true,
enableWebFont: true,
extraNetworkCardSize: 86,
extraNetworkFixedMode: 'fixed',
extraNetworkSidebarExpand: true,
extraNetworkSidebarWidth: 340,
i18n: 'en_US',
layoutHideFooter: false,
layoutSplitPreview: false,
liteAnimation: false,
logoCustomTitle: '',
logoCustomUrl: '',
logoType: 'lobe',
neutralColor: undefined,
primaryColor: undefined,
promptEditor: false,
promptTextareaType: 'resizable',
sidebarExpand: true,
sidebarFixedMode: 'fixed',
sidebarWidth: 280,
svgIcon: true,
};
export interface StroeState {
@ -71,11 +71,11 @@ export interface StroeState {
}
export const initialState: StroeState = {
currentTab: 'tab_txt2img',
latestVersion: DEFAULT_VERSION,
loading: true,
localeOptions: DEFAULT_LOCALE_OPTIONS,
setting: DEFAULT_SETTING,
themeMode: 'dark',
version: DEFAULT_VERSION,
currentTab: 'tab_txt2img',
latestVersion: DEFAULT_VERSION,
loading: true,
localeOptions: DEFAULT_LOCALE_OPTIONS,
setting: DEFAULT_SETTING,
themeMode: 'dark',
version: DEFAULT_VERSION,
};

View File

@ -5,7 +5,7 @@ const currentSetting = (s: Store) => ({ ...DEFAULT_SETTING, ...s.setting });
const currentTab = (s: Store) => s.currentTab;
const themeMode = (s: Store) => s.themeMode;
export const selectors = {
currentSetting,
currentTab,
themeMode,
currentSetting,
currentTab,
themeMode,
};

View File

@ -6,6 +6,6 @@ import { type StroeState, initialState } from './initialState';
export type Store = StoreAction & StroeState;
export const createStore: StateCreator<Store, [['zustand/devtools', never]]> = (...parameters) => ({
...initialState,
...createSettings(...parameters),
...initialState,
...createSettings(...parameters),
});

View File

@ -1,7 +1,7 @@
import { Theme, css } from 'antd-style';
export default (token: Theme) => {
return css`
return css`
.label-wrap {
transition: padding 400ms ${token.motionEaseOut};

View File

@ -2,7 +2,7 @@ import { Theme, css } from 'antd-style';
import { readableColor } from 'polished';
export default (token: Theme) => {
return css`
return css`
.gradio-group,
.gradio-row {
gap: 12px !important;

View File

@ -1,7 +1,7 @@
import { Theme, css } from 'antd-style';
export default (token: Theme) => {
return css`
return css`
#root {
/* sd-webui-prompt-all-in-one */
.physton-highlight-prompt {

View File

@ -1,7 +1,7 @@
import { Theme, css } from 'antd-style';
export default (token: Theme) => {
const galleryBackground = css`
const galleryBackground = css`
background-color: ${token.colorBgContainer};
background-image: linear-gradient(45deg, ${token.colorFillTertiary} 25%, transparent 25%),
linear-gradient(-45deg, ${token.colorFillTertiary} 25%, transparent 25%),
@ -16,7 +16,7 @@ export default (token: Theme) => {
border: 2px solid ${token.colorBorderSecondary} !important;
border-radius: ${token.borderRadius}px !important;
`;
return css`
return css`
.livePreview,
.gradio-gallery,
.gradio-image,

View File

@ -1,7 +1,7 @@
import { Theme, css } from 'antd-style';
export default (token: Theme) => {
return css`
return css`
.block.gradio-checkbox {
margin: 0 !important;
}

View File

@ -1,7 +1,7 @@
import { css } from 'antd-style';
export default () => {
return css`
return css`
[id$='_settings'] {
label.svelte-1ojmf70 {
overflow: hidden;

View File

@ -17,21 +17,21 @@ import tabs from './components/tabs';
import tokens from './tokens';
const GlobalStyle = createGlobalStyle(({ theme }) => [
tokens(theme),
antdOverride(theme),
button(theme),
container(theme),
gallery(theme),
input(theme),
label(),
collapse(theme),
options(theme),
progress(theme),
slider(theme),
table(theme),
tabs(theme),
extensions(theme),
lightboxModal(theme),
tokens(theme),
antdOverride(theme),
button(theme),
container(theme),
gallery(theme),
input(theme),
label(),
collapse(theme),
options(theme),
progress(theme),
slider(theme),
table(theme),
tabs(theme),
extensions(theme),
lightboxModal(theme),
]);
export default GlobalStyle;

View File

@ -1,63 +1,63 @@
export const kitchenPrimary = {
dark: {
colorPrimary: '#007AFF',
colorPrimaryActive: '#1554ad',
colorPrimaryBg: '#111a2c',
colorPrimaryBgHover: '#112545',
colorPrimaryBorder: '#15325b',
colorPrimaryBorderHover: '#15417e',
colorPrimaryHover: '#3c89e8',
colorPrimaryText: '#1668dc',
colorPrimaryTextActive: '#1554ad',
colorPrimaryTextHover: '#3c89e8',
},
light: {
colorPrimary: '#007AFF',
colorPrimaryActive: '#0958d9',
colorPrimaryBg: '#e6f4ff',
colorPrimaryBgHover: '#bae0ff',
colorPrimaryBorder: '#91caff',
colorPrimaryBorderHover: '#69b1ff',
colorPrimaryHover: '#4096ff',
colorPrimaryText: '#1677ff',
colorPrimaryTextActive: '#0958d9',
colorPrimaryTextHover: '#4096ff',
},
dark: {
colorPrimary: '#007AFF',
colorPrimaryActive: '#1554ad',
colorPrimaryBg: '#111a2c',
colorPrimaryBgHover: '#112545',
colorPrimaryBorder: '#15325b',
colorPrimaryBorderHover: '#15417e',
colorPrimaryHover: '#3c89e8',
colorPrimaryText: '#1668dc',
colorPrimaryTextActive: '#1554ad',
colorPrimaryTextHover: '#3c89e8',
},
light: {
colorPrimary: '#007AFF',
colorPrimaryActive: '#0958d9',
colorPrimaryBg: '#e6f4ff',
colorPrimaryBgHover: '#bae0ff',
colorPrimaryBorder: '#91caff',
colorPrimaryBorderHover: '#69b1ff',
colorPrimaryHover: '#4096ff',
colorPrimaryText: '#1677ff',
colorPrimaryTextActive: '#0958d9',
colorPrimaryTextHover: '#4096ff',
},
};
export const kitchenNeutral = {
dark: {
colorBgContainer: '#1f1f1f',
colorBgElevated: '#222',
colorBgLayout: '#181818',
colorBgSpotlight: '#444',
colorBorder: '#444',
colorBorderSecondary: '#333',
colorFill: 'rgb(255 255 255 / 18%)',
colorFillQuaternary: 'rgb(255 255 255 / 4%)',
colorFillSecondary: 'rgb(255 255 255 / 12%)',
colorFillTertiary: 'rgb(255 255 255 / 8%)',
colorNeutral: '#666',
colorText: 'rgb(255 255 255 / 85%)',
colorTextQuaternary: 'rgb(255 255 255 / 25%)',
colorTextSecondary: 'rgb(255 255 255 / 65%)',
colorTextTertiary: 'rgb(255 255 255 / 45%)',
},
light: {
colorBgContainer: '#fff',
colorBgElevated: '#fff',
colorBgLayout: '#f7f7f7',
colorBgSpotlight: 'rgb(0 0 0 / 85%)',
colorBorder: '#ddd',
colorBorderSecondary: '#eee',
colorFill: 'rgb(0 0 0 / 15%)',
colorFillQuaternary: 'rgb(0 0 0 / 2%)',
colorFillSecondary: 'rgb(0 0 0 / 6%)',
colorFillTertiary: 'rgb(0 0 0 / 4%)',
colorNeutral: '#666',
colorText: 'rgb(0 0 0 / 88%)',
colorTextQuaternary: 'rgb(0 0 0 / 25%)',
colorTextSecondary: 'rgb(0 0 0 / 65%)',
colorTextTertiary: 'rgb(0 0 0 / 45%)',
},
dark: {
colorBgContainer: '#1f1f1f',
colorBgElevated: '#222',
colorBgLayout: '#181818',
colorBgSpotlight: '#444',
colorBorder: '#444',
colorBorderSecondary: '#333',
colorFill: 'rgb(255 255 255 / 18%)',
colorFillQuaternary: 'rgb(255 255 255 / 4%)',
colorFillSecondary: 'rgb(255 255 255 / 12%)',
colorFillTertiary: 'rgb(255 255 255 / 8%)',
colorNeutral: '#666',
colorText: 'rgb(255 255 255 / 85%)',
colorTextQuaternary: 'rgb(255 255 255 / 25%)',
colorTextSecondary: 'rgb(255 255 255 / 65%)',
colorTextTertiary: 'rgb(255 255 255 / 45%)',
},
light: {
colorBgContainer: '#fff',
colorBgElevated: '#fff',
colorBgLayout: '#f7f7f7',
colorBgSpotlight: 'rgb(0 0 0 / 85%)',
colorBorder: '#ddd',
colorBorderSecondary: '#eee',
colorFill: 'rgb(0 0 0 / 15%)',
colorFillQuaternary: 'rgb(0 0 0 / 2%)',
colorFillSecondary: 'rgb(0 0 0 / 6%)',
colorFillTertiary: 'rgb(0 0 0 / 4%)',
colorNeutral: '#666',
colorText: 'rgb(0 0 0 / 88%)',
colorTextQuaternary: 'rgb(0 0 0 / 25%)',
colorTextSecondary: 'rgb(0 0 0 / 65%)',
colorTextTertiary: 'rgb(0 0 0 / 45%)',
},
};

View File

@ -2,7 +2,7 @@ import { type Theme, css } from 'antd-style';
import { readableColor } from 'polished';
export default (token: Theme) => {
return css`
return css`
:root,
.dark {
--primary-50: ${token.geekblue1};

View File

@ -6,7 +6,7 @@ import i18nOptions from '@/../locales/options.json';
export type I18n = (typeof i18nOptions)[number]['value'];
export const resources = {
translation,
translation,
} as const;
type TranslationKeys = keyof typeof translation;