feat: Fit SD WebUI 1.9

This commit is contained in:
canisminor1990 2024-05-15 22:25:16 +08:00
parent 77102c0e1e
commit 3e01da0cea
35 changed files with 1041 additions and 404 deletions

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -25,6 +25,7 @@
"title": "Themenfeedback"
},
"themeSetting": {
"desc": "Einstellungen für Thema und Layout",
"title": "Themen-Einstellungen"
}
},

View File

@ -1,8 +1,8 @@
{
"brand": {
"custom": "Custom",
"kitchen": "Kitchen",
"lobe": "LobeHub",
"custom": "Custom"
"lobe": "LobeHub"
},
"cancel": "Cancel",
"confirm": "Confirm",
@ -10,34 +10,35 @@
"initializing": "StableDiffusion / LobeTheme is initializing, please wait..."
},
"footer": {
"resources": "Resources",
"community": "Community",
"help": "Help",
"moreProducts": "More Products"
"moreProducts": "More Products",
"resources": "Resources"
},
"header": {
"feedback": "Feedback",
"switchTheme": "Switch Light/Dark Theme",
"setting": "Setting"
"setting": "Setting",
"switchTheme": "Switch Light/Dark Theme"
},
"modal": {
"themeFeedback": {
"title": "Theme Feedback"
},
"themeSetting": {
"desc": "Preferences and Layout Settings",
"title": "Theme Settings"
}
},
"prompt": {
"area": {
"object": "Object Selection",
"attribute": "Attribute Selection",
"object": "Object Selection",
"tag": "Tag Selection"
},
"load": "Load Prompt",
"set": "Set Prompt",
"negative": "Negative",
"positive": "Positive"
"positive": "Positive",
"set": "Set Prompt"
},
"setting": {
"button": {
@ -45,152 +46,152 @@
"submit": "Apply and Restart Interface"
},
"confirmPageUnload": {
"title": "Confirmation on page leaving",
"desc": "Helps prevent loss of unsaved data"
"desc": "Helps prevent loss of unsaved data",
"title": "Confirmation on page leaving"
},
"customFont": {
"title": "Custom Font",
"desc": "When enabled, it will automatically load a webfont to enhance the display of text in Chinese, English, and code"
"desc": "When enabled, it will automatically load a webfont to enhance the display of text in Chinese, English, and code",
"title": "Custom Font"
},
"customLogo": {
"title": "Custom Logo",
"desc": "Support URL / Base64 / Emoji symbols"
"desc": "Support URL / Base64 / Emoji symbols",
"title": "Custom Logo"
},
"customTitle": {
"title": "Custom Title",
"desc": "Custom Logo Title"
"desc": "Custom Logo Title",
"title": "Custom Title"
},
"extraNetworkSidebar": {
"defaultCardSize": {
"title": "Model Cover Size",
"desc": "Default value of model cover size when starting"
"desc": "Default value of model cover size when starting",
"title": "Model Cover Size"
},
"defaultExpand": {
"title": "Default Expand",
"desc": "Whether to expand the sidebar by default when starting"
"desc": "Whether to expand the sidebar by default when starting",
"title": "Default Expand"
},
"defaultWidth": {
"title": "Default Width",
"desc": "Default width of the sidebar when starting"
"desc": "Default width of the sidebar when starting",
"title": "Default Width"
},
"displayMode": {
"title": "Display Mode",
"desc": "Fixed as grid mode for constant display, auto-expand when the mouse moves to the side in floating mode"
"desc": "Fixed as grid mode for constant display, auto-expand when the mouse moves to the side in floating mode",
"title": "Display Mode"
},
"enable": {
"title": "Enable",
"desc": "Enable the extra network sidebar on the right side"
"desc": "Enable the extra network sidebar on the right side",
"title": "Enable"
}
},
"tab": {
"appearance": "Appearance",
"sidebar": "Sidebar",
"layout": "Layout",
"experimental": "Experimental"
},
"group": {
"experimental": "Experimental Features",
"extraNetworkSidebar": "Extra Network Sidebar",
"layout": "Layout Settings",
"promptTextarea": "Prompt Textbox",
"quickSettingSidebar": "Quick Setting Sidebar",
"theme": "Theme Settings",
"experimental": "Experimental Features"
},
"imageInfo": {
"title": "Image Info Alternative",
"desc": "Display better image information in the generated image"
"theme": "Theme Settings"
},
"hideFooter": {
"title": "Hide Footer",
"desc": "Hide the theme footer and only display the default footer of stable diffusion webui"
"desc": "Hide the theme footer and only display the default footer of stable diffusion webui",
"title": "Hide Footer"
},
"imageInfo": {
"desc": "Display better image information in the generated image",
"title": "Image Info Alternative"
},
"language": {
"title": "Language",
"desc": "Lobe Theme language"
"desc": "Lobe Theme language",
"title": "Language"
},
"logoType": {
"title": "Logo Type",
"desc": "Logo Type",
"preview": "Preview"
"preview": "Preview",
"title": "Logo Type"
},
"neutralColor": {
"title": "Neutral Color",
"desc": "Customize different shades of gray with different color tendencies, the second one is the original Kitchen neutral color"
"desc": "Customize different shades of gray with different color tendencies, the second one is the original Kitchen neutral color",
"title": "Neutral Color"
},
"primaryColor": {
"title": "Primary Color",
"desc": "Custom primary color, the second one is the original Kitchen theme color"
"desc": "Custom primary color, the second one is the original Kitchen theme color",
"title": "Primary Color"
},
"promptDisplayMode": {
"title": "Prompt Display Mode",
"desc": "Fixed height or auto height with draggable resize support",
"resizable": "Resizable",
"scroll": "Scroll"
"scroll": "Scroll",
"title": "Prompt Display Mode"
},
"promptEditor": {
"title": "Prompt Editor",
"desc": "Provide a simple prompt editor at the top of the quick setting sidebar"
"desc": "Provide a simple prompt editor at the top of the quick setting sidebar",
"title": "Prompt Editor"
},
"promptHighlight": {
"title": "Prompt Syntax Highlighting",
"desc": "Automatically colorize prompt display according to the Stable Diffusion syntax rules"
"desc": "Automatically colorize prompt display according to the Stable Diffusion syntax rules",
"title": "Prompt Syntax Highlighting"
},
"quickSettingSidebar": {
"defaultExpand": {
"title": "Default Expand",
"desc": "Whether to expand the sidebar by default when starting"
"desc": "Whether to expand the sidebar by default when starting",
"title": "Default Expand"
},
"defaultWidth": {
"title": "Default Width",
"desc": "Default width of the sidebar when starting"
"desc": "Default width of the sidebar when starting",
"title": "Default Width"
},
"displayMode": {
"title": "Display Mode",
"desc": "Fixed as grid mode for constant display, auto-expand when the mouse moves to the side in floating mode"
"desc": "Fixed as grid mode for constant display, auto-expand when the mouse moves to the side in floating mode",
"title": "Display Mode"
},
"enable": {
"title": "Enable",
"desc": "Enable the quick setting sidebar on the left side"
"desc": "Enable the quick setting sidebar on the left side",
"title": "Enable"
}
},
"reduceAnimation": {
"title": "Reduce Animation",
"desc": "Reduce the blur effect and background flow color, which can improve smoothness and save CPU usage"
"desc": "Reduce the blur effect and background flow color, which can improve smoothness and save CPU usage",
"title": "Reduce Animation"
},
"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)"
"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)",
"title": "Split Previewer"
},
"svgIcons": {
"title": "SVG Icons",
"desc": "Replace all Emoji icons in stable diffusion webui with SVG icons globally"
"desc": "Replace all Emoji icons in stable diffusion webui with SVG icons globally",
"title": "SVG Icons"
},
"tab": {
"appearance": "Appearance",
"experimental": "Experimental",
"layout": "Layout",
"sidebar": "Sidebar"
}
},
"share": "Share",
"shareModal": {
"download": "Download Screenshot",
"imageType": "Image Format",
"screenshot": "Screenshot",
"info": "Image Info",
"screenshot": "Screenshot",
"settings": "Export Settings",
"withBackground": "Include Background Image",
"withFooter": "Include Footer",
"warn": "Please Generate Image First",
"showNegative": "Show Negative Promot",
"showConfig": "Show Generate Config",
"showAllImages": "Show All Images",
"title": "Image Name",
"showConfig": "Show Generate Config",
"showNegative": "Show Negative Promot",
"tabs": {
"info": "Info",
"settings": "Settings"
}
},
"title": "Image Name",
"warn": "Please Generate Image First",
"withBackground": "Include Background Image",
"withFooter": "Include Footer"
},
"sidebar": {
"extraNetwork": "Extra Network",
"quickSetting": "Quick Setting",
"mode": {
"fixed": "Fixed",
"float": "Float"
}
},
"quickSetting": "Quick Setting"
}
}

View File

@ -25,6 +25,7 @@
"title": "Comentarios sobre el tema"
},
"themeSetting": {
"desc": "Preferencias y configuración de diseño",
"title": "Configuración del tema"
}
},

View File

@ -25,6 +25,7 @@
"title": "Retour sur le thème"
},
"themeSetting": {
"desc": "Paramètres de thème et de mise en page",
"title": "Paramètres du thème"
}
},

View File

@ -25,6 +25,7 @@
"title": "テーマフィードバック"
},
"themeSetting": {
"desc": "テーマとレイアウト設定",
"title": "テーマ設定"
}
},

View File

@ -25,6 +25,7 @@
"title": "테마 피드백"
},
"themeSetting": {
"desc": "테마 및 레이아웃 설정",
"title": "테마 설정"
}
},

View File

@ -25,6 +25,7 @@
"title": "Feedback do Tema"
},
"themeSetting": {
"desc": "Preferências e configurações de layout",
"title": "Configurações do Tema"
}
},

View File

@ -25,6 +25,7 @@
"title": "Обратная связь по теме"
},
"themeSetting": {
"desc": "Настройки темы и макета",
"title": "Настройки темы"
}
},

View File

@ -25,6 +25,7 @@
"title": "Tema Geri Bildirimi"
},
"themeSetting": {
"desc": "Tema ve Düzen Ayarları",
"title": "Tema Ayarları"
}
},

View File

@ -25,7 +25,8 @@
"title": "主题反馈"
},
"themeSetting": {
"title": "主题设置"
"title": "主题设置",
"desc": "偏好与布局设置"
}
},
"prompt": {

View File

@ -25,6 +25,7 @@
"title": "主題反饋"
},
"themeSetting": {
"desc": "偏好與版面設定",
"title": "主題設置"
}
},

View File

@ -2,7 +2,7 @@
"name": "sd-webui-lobe-theme",
"version": "3.4.10",
"private": true,
"description": "The modern theme for stable diffusion webui, exquisite interface design, highly customizable UI, and efficiency boosting features.",
"description": "LobeThem: The Modern Theme for Stable Diffusion WebUI, Exquisite interface design, Highly customizable UI, and Efficiency boosting features.",
"keywords": [
"lobehub",
"stable-diffusion-webui",
@ -67,67 +67,68 @@
]
},
"dependencies": {
"@bluelovers/auto1111-pnginfo": "^2.0.1",
"@lobehub/ui": "latest",
"@rollup/rollup-win32-x64-msvc": "^4.13.0",
"ahooks": "^3",
"antd": "^5",
"antd-style": "latest",
"consola": "^3",
"dayjs": "^1",
"i18next": "^23",
"i18next-http-backend": "^2",
"lodash-es": "^4",
"@bluelovers/auto1111-pnginfo": "^2.0.2",
"@lobehub/ui": "^1.138.24",
"@rollup/rollup-win32-x64-msvc": "^4.17.2",
"ahooks": "^3.7.11",
"antd": "5.17.0",
"antd-style": "^3.6.2",
"consola": "^3.2.3",
"dayjs": "^1.11.11",
"i18next": "^23.11.4",
"i18next-http-backend": "^2.5.1",
"lodash-es": "^4.17.21",
"lucide-react": "latest",
"lucide-static": "latest",
"modern-screenshot": "^4",
"polished": "^4",
"react": "^18",
"react-dnd": "^16",
"react-dnd-html5-backend": "^16",
"react-dom": "^18",
"react-helmet": "^6",
"react-i18next": "^13",
"react-layout-kit": "^1",
"react-rnd": "^10",
"react-tag-input": "^6",
"semver": "^7",
"shikiji": "^0.9",
"swr": "^2",
"zustand": "^4.4.1",
"zustand-utils": "^1.3.1"
"modern-screenshot": "^4.4.39",
"polished": "^4.3.1",
"react": "^18.3.1",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.3.1",
"react-helmet": "^6.1.0",
"react-i18next": "^13.5.0",
"react-layout-kit": "^1.9.0",
"react-rnd": "^10.4.10",
"react-tag-input": "^6.9.0",
"semver": "^7.6.2",
"shikiji": "^0.9.19",
"swr": "^2.2.5",
"url-join": "^5.0.0",
"zustand": "^4.5.2",
"zustand-utils": "^1.3.2"
},
"devDependencies": {
"@commitlint/cli": "^18",
"@lobehub/lint": "latest",
"@testing-library/jest-dom": "^6",
"@testing-library/react": "^14",
"@types/lodash-es": "^4",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/react-helmet": "^6",
"@types/react-tag-input": "^6",
"@types/semver": "^7",
"@vitejs/plugin-react-swc": "^3",
"@vitest/coverage-v8": "^1",
"commitlint": "^18",
"dotenv": "^16",
"eslint": "^8",
"fast-deep-equal": "^3",
"husky": "^8",
"jsdom": "^23.0.0",
"lint-staged": "^15",
"prettier": "^3",
"query-string": "^8",
"remark": "^14",
"remark-cli": "^11",
"semantic-release": "^21",
"stylelint": "^15",
"terser": "^5",
"typescript": "^5",
"vite": "^5",
"vitest": "latest"
"@commitlint/cli": "^18.6.1",
"@lobehub/lint": "^1.23.4",
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/react": "^14.3.1",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.12.12",
"@types/react": "^18.3.2",
"@types/react-dom": "^18.3.0",
"@types/react-helmet": "^6.1.11",
"@types/react-tag-input": "^6.6.6",
"@types/semver": "^7.5.8",
"@vitejs/plugin-react-swc": "^3.6.0",
"@vitest/coverage-v8": "^1.6.0",
"commitlint": "^18.6.1",
"dotenv": "^16.4.5",
"eslint": "^8.57.0",
"fast-deep-equal": "^3.1.3",
"husky": "^8.0.3",
"jsdom": "^23.2.0",
"lint-staged": "^15.2.2",
"prettier": "^3.2.5",
"query-string": "^8.2.0",
"remark": "^14.0.3",
"remark-cli": "^11.0.0",
"semantic-release": "^21.1.2",
"stylelint": "^15.11.0",
"terser": "^5.31.0",
"typescript": "^5.4.5",
"vite": "^5.2.11",
"vitest": "~1.2.2"
},
"publishConfig": {
"access": "public",

View File

@ -2,6 +2,7 @@ import { LayoutHeader, LayoutMain, LayoutSidebar } from '@lobehub/ui';
import isEqual from 'fast-deep-equal';
import { memo, useEffect } from 'react';
import StructuredData from '@/components/StructuredData';
import PromptFormator from '@/features/PromptFormator';
import '@/locales/config';
import ImageInfo from '@/modules/ImageInfo/page';
@ -35,6 +36,7 @@ const Index = memo(() => {
return (
<>
<StructuredData />
<GlobalStyle />
<LayoutHeader headerHeight={HEADER_HEIGHT}>
<Header />

View File

@ -67,6 +67,7 @@ export const Layout = memo<PropsWithChildren>(({ children }) => {
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, viewport-fit=cover, user-scalable=no"
name="viewport"
/>
<title>{TITLE}</title>
<meta content={TITLE} name="apple-mobile-web-app-title" />
<meta content={TITLE} name="application-name" />
<meta content={DESC} name="description" />
@ -76,7 +77,29 @@ export const Layout = memo<PropsWithChildren>(({ children }) => {
<meta content="yes" name="apple-mobile-web-app-capable" />
<meta content={TITLE} name="apple-mobile-web-app-title" />
<meta content="black-translucent" name="apple-mobile-web-app-status-bar-style" />
<meta content={TITLE} name="apple-mobile-web-app-title" />
<meta content="yes" name="apple-mobile-web-app-capable" />
<meta content="index,follow" name="robots" />
<link href={manifest(genAssets)} rel="manifest" />
<meta content={TITLE} property="og:title" />
<meta content={DESC} property="og:description" />
<meta content="https://github.com/lobehub/sd-webui-lobe-theme" property="og:url" />
<meta content={TITLE} property="og:site_name" />
<meta content="en-US" property="og:locale" />
<meta
content="https://repository-images.githubusercontent.com/606329910/7fd79db5-fd91-450c-9e95-8ccce8ffdc0b"
property="og:image"
/>
<meta content="website" property="og:type" />
<meta content="summary_large_image" name="twitter:card" />
<meta content="@lobehub" name="twitter:site" />
<meta content={TITLE} name="twitter:title" />
<meta content={DESC} name="twitter:description" />
<meta
content="https://repository-images.githubusercontent.com/606329910/7fd79db5-fd91-450c-9e95-8ccce8ffdc0b"
name="twitter:image"
/>
<link href="https://github.com/lobehub/sd-webui-lobe-theme" rel="canonical" />
</Helmet>
<GlobalLayout>
{storeLoading === false && loading === false ? children : <Loading />}

View File

@ -0,0 +1,26 @@
import { FC } from 'react';
import pkg from '@/../package.json';
import { ldModule } from '@/components/StructuredData/ld';
const TITLE = 'Stable Diffusion · LobeHub';
const DESC = pkg.description;
const StructuredData: FC = () => {
const ld = ldModule.generate({
description: DESC,
image:
'https://repository-images.githubusercontent.com/606329910/7fd79db5-fd91-450c-9e95-8ccce8ffdc0b',
title: TITLE,
url: '/',
});
return (
<script
dangerouslySetInnerHTML={{ __html: JSON.stringify(ld) }}
id="structured-data"
type="application/ld+json"
/>
);
};
export default StructuredData;

View File

@ -0,0 +1,216 @@
import urlJoin from 'url-join';
import pkg from '@/../package.json';
import { EMAIL_BUSINESS, EMAIL_SUPPORT, OFFICIAL_SITE, SITE_URL, X } from '@/const/url';
const LAST_MODIFIED = new Date().toISOString();
export const AUTHOR_LIST = {
arvinxx: {
avatar: 'https://avatars.githubusercontent.com/u/28616219?v=4',
desc: 'Founder, Design Engineer',
name: 'Arvin Xu',
url: 'https://github.com/arvinxx',
},
canisminor: {
avatar: 'https://avatars.githubusercontent.com/u/17870709?v=4',
desc: 'Founder, Design Engineer',
name: 'CanisMinor',
url: 'https://github.com/arvinxx',
},
lobehub: {
avatar: 'https://avatars.githubusercontent.com/u/131470832?v=4',
desc: 'Official Account',
name: 'LobeHub',
url: 'https://github.com/lobehub',
},
};
class Ld {
generate({
image = '/og/cover.png',
url,
title,
description,
date,
webpage = {
enable: true,
},
}: {
date?: string;
description: string;
image?: string;
title: string;
url: string;
webpage?: {
enable?: boolean;
search?: boolean;
};
}) {
return {
'@context': 'https://schema.org',
'@graph': [
this.genWebSite(),
webpage?.enable &&
this.genWebPage({
...webpage,
date,
description,
image,
title,
url,
}),
image && this.genImageObject({ image, url }),
this.genOrganization(),
].filter(Boolean),
};
}
genOrganization() {
return {
'@id': this.getId(SITE_URL, '#organization'),
'@type': 'Organization',
'alternateName': 'LobeTheme',
'contactPoint': {
'@type': 'ContactPoint',
'contactType': 'customer support',
'email': EMAIL_SUPPORT,
},
'description':
'We are a group of e/acc design-engineers, hoping to provide modern design components and tools for AIGC, and creating a technology-driven forum, fostering knowledge interaction and the exchange of ideas that may culminate in mutual inspiration and collaborative innovation.',
'email': EMAIL_BUSINESS,
'founders': [this.getAuthors(['arvinxx']), this.getAuthors(['canisminor'])],
'image': urlJoin(OFFICIAL_SITE, '/icon-512x512.png'),
'logo': {
'@type': 'ImageObject',
'height': 512,
'url': urlJoin(OFFICIAL_SITE, '/icon-512x512.png'),
'width': 512,
},
'name': 'LobeHub',
'sameAs': [
X,
'https://github.com/lobehub',
'https://medium.com/@lobehub',
'https://www.youtube.com/@lobehub',
],
'url': OFFICIAL_SITE,
};
}
getAuthors(ids: string[] = []) {
const defaultAuthor = {
'@id': this.getId(SITE_URL, '#organization'),
'@type': 'Organization',
};
if (!ids || ids.length === 0) return defaultAuthor;
if (ids.length === 1 && ids[0] === 'lobehub') return defaultAuthor;
const personId = ids.find((id) => id !== 'lobehub');
if (!personId) return defaultAuthor;
const person = (AUTHOR_LIST as any)?.[personId];
if (!person) return defaultAuthor;
return {
'@type': 'Person',
'name': person.name,
'url': person.url,
};
}
genWebPage({
date,
image,
search,
description,
title,
url,
}: {
breadcrumbs?: { title: string; url: string }[];
date?: string;
description: string;
image?: string;
search?: boolean;
title: string;
url: string;
}) {
const fixedUrl = this.fixUrl(url);
const dateCreated = date ? new Date(date).toISOString() : LAST_MODIFIED;
const dateModified = date ? new Date(date).toISOString() : LAST_MODIFIED;
const baseInfo: any = {
'@id': fixedUrl,
'@type': 'WebPage',
'about': {
'@id': this.getId(SITE_URL, '#organization'),
},
'breadcrumbs': {
'@id': this.getId(fixedUrl, '#breadcrumb'),
},
'dateModified': dateModified,
'datePublished': dateCreated,
'description': description,
'image': {
'@id': this.getId(fixedUrl, '#primaryimage'),
},
'inLanguage': 'en-US',
'isPartOf': {
'@id': this.getId(SITE_URL, '#website'),
},
'name': this.fixTitle(title),
'primaryImageOfPage': {
'@id': this.getId(fixedUrl, '#primaryimage'),
},
'thumbnailUrl': image,
};
if (search) {
baseInfo.potentialAction = {
'@type': 'SearchAction',
'query-input': 'required name=search_term_string',
'target': `${fixedUrl}?q={search_term_string}`,
};
}
return baseInfo;
}
genImageObject({ image, url }: { image: string; url: string }) {
const fixedUrl = this.fixUrl(url);
return {
'@id': this.getId(fixedUrl, '#primaryimage'),
'@type': 'ImageObject',
'contentUrl': image,
'inLanguage': 'en-US',
'url': image,
};
}
genWebSite() {
const baseInfo: any = {
'@id': this.getId(SITE_URL, '#website'),
'@type': 'WebSite',
'description': pkg.description,
'inLanguage': 'en-US',
'name': 'LobeTheme',
'publisher': {
'@id': this.getId(SITE_URL, '#organization'),
},
'url': SITE_URL,
};
return baseInfo;
}
private getId(url: string, id: string) {
return [url, id].join('/');
}
private fixTitle(title: string) {
return title.includes('LobeTheme') ? title : `${title} · LobeTheme`;
}
private fixUrl(url: string) {
return urlJoin(SITE_URL, url);
}
}
export const ldModule = new Ld();

View File

@ -2,10 +2,14 @@ import pkg from '@/../package.json';
export const DISCORD_URL = 'https://discord.gg/AYFPHvv2jT';
export const SPONSOR_URL = 'https://opencollective.com/lobehub';
export const SPONSOR_IMG = 'https://readme-wizard.lobehub.com/api/sponsor';
export const GISCUS_REPO_ID = 'R_kgDOJCPcNg';
export const GITHUB_REPO_URL = pkg.homepage;
export const REPO_NAME = GITHUB_REPO_URL.replace(
'https://github.com/',
'',
) as `${string}/${string}`;
export const OFFICIAL_SITE = 'https://lobehub.com/';
export const SITE_URL = location.origin;
export const EMAIL_SUPPORT = 'support@lobehub.com';
export const EMAIL_BUSINESS = 'hello@lobehub.com';
export const X = 'https://x.com/lobehub';

View File

@ -36,11 +36,14 @@ export const useStyles = createStyles(
overflow: unset;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(${size}px, 1fr));
flex: none !important;
gap: 8px;
height: unset;
min-height: unset;
border: unset !important;
.name {
background: unset !important;
}
@ -55,6 +58,19 @@ export const useStyles = createStyles(
}
}
.extra-network-dirs {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-bottom: 12px;
> button.lg.secondary.gradio-button {
padding: 4px 8px;
font-size: 12px;
line-height: 1;
}
}
.extra-networks {
.pending {
opacity: 1 !important;
@ -78,6 +94,37 @@ export const useStyles = createStyles(
display: none;
}
}
.extra-networks-controls-div {
height: unset !important;
}
.extra-network-control {
position: relative;
flex: none;
flex-wrap: wrap;
gap: 8px;
.extra-network-control--search {
width: 100%;
}
small {
display: none;
}
> div:has(i) {
position: relative;
display: flex;
flex: none;
height: 32px;
background: ${token.colorFillTertiary};
border-radius: ${token.borderRadius}px;
}
}
}
.extra-network-subdirs {

View File

@ -1,7 +1,6 @@
import { Form, Swatches } from '@lobehub/ui';
import { Input, Segmented, Select, Switch } from 'antd';
import isEqual from 'fast-deep-equal';
import { Palette } from 'lucide-react';
import { memo, useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
@ -153,7 +152,7 @@ const SettingForm = memo(() => {
valuePropName: 'checked',
},
],
icon: Palette,
title: t('setting.group.theme'),
}),
[
@ -173,6 +172,7 @@ const SettingForm = memo(() => {
onFinish={onFinish}
onValuesChange={(_, v) => setRawSetting(v)}
style={{ flex: 1 }}
variant={'pure'}
/>
);
});

View File

@ -1,7 +1,6 @@
import { Form } from '@lobehub/ui';
import { Switch } from 'antd';
import isEqual from 'fast-deep-equal';
import { Puzzle, TextCursorInput } from 'lucide-react';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@ -31,7 +30,6 @@ const SettingForm = memo(() => {
valuePropName: 'checked',
},
],
icon: Puzzle,
title: t('setting.group.experimental'),
}),
[],
@ -55,7 +53,6 @@ const SettingForm = memo(() => {
valuePropName: 'checked',
},
],
icon: TextCursorInput,
title: t('setting.group.promptTextarea'),
}),
[],
@ -68,6 +65,7 @@ const SettingForm = memo(() => {
items={[experimental, promptTextarea]}
onFinish={onFinish}
style={{ flex: 1 }}
variant={'pure'}
/>
);
});

View File

@ -1,7 +1,6 @@
import { Form } from '@lobehub/ui';
import { Segmented, Switch } from 'antd';
import isEqual from 'fast-deep-equal';
import { Layout, TextCursorInput } from 'lucide-react';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@ -38,7 +37,7 @@ const SettingForm = memo(() => {
valuePropName: 'checked',
},
],
icon: Layout,
title: t('setting.group.layout'),
}),
[],
@ -67,7 +66,7 @@ const SettingForm = memo(() => {
name: 'promptTextareaType',
},
],
icon: TextCursorInput,
title: t('setting.group.promptTextarea'),
}),
[],
@ -80,6 +79,7 @@ const SettingForm = memo(() => {
items={[layout, promptTextarea]}
onFinish={onFinish}
style={{ flex: 1 }}
variant={'pure'}
/>
);
});

View File

@ -1,7 +1,6 @@
import { Form } from '@lobehub/ui';
import { InputNumber, Segmented, Switch } from 'antd';
import isEqual from 'fast-deep-equal';
import { PanelLeftClose, PanelRightClose } from 'lucide-react';
import { memo, useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
@ -67,7 +66,7 @@ const SettingForm = memo(() => {
name: 'sidebarWidth',
},
],
icon: PanelLeftClose,
title: t('setting.group.quickSettingSidebar'),
}),
[rawSetting.enableSidebar],
@ -126,7 +125,7 @@ const SettingForm = memo(() => {
name: 'extraNetworkCardSize',
},
],
icon: PanelRightClose,
title: t('setting.group.extraNetworkSidebar'),
}),
[rawSetting.enableExtraNetworkSidebar],
@ -140,6 +139,7 @@ const SettingForm = memo(() => {
onFinish={onFinish}
onValuesChange={(_, v) => setRawSetting(v)}
style={{ flex: 1 }}
variant={'pure'}
/>
);
});

View File

@ -0,0 +1,41 @@
import { Logo } from '@lobehub/ui';
import { createStyles } from 'antd-style';
import { memo } from 'react';
import { Flexbox, FlexboxProps } from 'react-layout-kit';
const useStyles = createStyles(({ token, css }) => ({
logoLink: css`
height: 20px;
color: inherit;
&:hover {
color: ${token.colorLink};
}
`,
}));
const BrandWatermark = memo<Omit<FlexboxProps, 'children'>>(({ style, ...rest }) => {
const { styles, theme } = useStyles();
return (
<Flexbox
align={'center'}
flex={'none'}
gap={4}
horizontal
style={{ color: theme.colorTextDescription, fontSize: 12, ...style }}
{...rest}
>
<span>Powered by</span>
<a
className={styles.logoLink}
href={'https://lobehub.com'}
rel="noreferrer"
target={'_blank'}
>
<Logo size={20} type={'text'} />
</a>
</Flexbox>
);
});
export default BrandWatermark;

View File

@ -1,47 +0,0 @@
import { Icon, List } from '@lobehub/ui';
import { createStyles } from 'antd-style';
import { type LucideIcon } from 'lucide-react';
import { CSSProperties, ReactNode, memo } from 'react';
const { Item } = List;
const useStyles = createStyles(({ css, token }) => ({
container: css`
position: relative;
padding: 16px;
border-radius: ${token.borderRadius}px;
div {
overflow: visible;
font-size: 16px;
font-weight: 500;
}
`,
}));
export interface ItemProps {
active?: boolean;
className?: string;
icon: LucideIcon;
label: ReactNode;
onClick?: () => void;
style?: CSSProperties;
}
const SettingItem = memo<ItemProps>(
({ label, icon, active = false, style, className, onClick }) => {
const { cx, styles } = useStyles();
return (
<Item
active={active}
avatar={<Icon icon={icon} size={{ fontSize: 16 }} />}
className={cx(styles.container, className)}
onClick={onClick}
style={style}
title={label as string}
/>
);
},
);
export default SettingItem;

View File

@ -0,0 +1,97 @@
import { Menu as AntdMenu, MenuProps as AntdMenuProps, ConfigProvider } from 'antd';
import { createStyles } from 'antd-style';
import { memo } from 'react';
const useStyles = createStyles(({ css, token, prefixCls }) => ({
compact: css`
display: flex;
flex-direction: column;
gap: 0.125rem;
`,
menu: css`
flex: 1;
background: transparent;
border: none !important;
.${prefixCls}-menu-item-divider {
margin-block: 0.125rem;
border-color: ${token.colorFillTertiary};
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
}
.${prefixCls}-menu-item, .${prefixCls}-menu-submenu-title {
display: flex;
gap: 0.75rem;
align-items: center;
height: unset;
min-height: 2rem;
padding: 0.375rem 0.75rem;
line-height: 2;
.anticon + .${prefixCls}-menu-title-content {
margin-inline-start: 0;
}
}
.${prefixCls}-menu-item-selected {
.${prefixCls}-menu-item-icon svg {
color: ${token.colorText};
}
}
.${prefixCls}-menu-item-icon svg {
color: ${token.colorTextSecondary};
}
.${prefixCls}-menu-title-content {
flex: 1;
}
`,
}));
export interface MenuProps extends AntdMenuProps {
variant?: 'default' | 'compact';
}
const Menu = memo<MenuProps>(({ className, selectable = false, variant, ...rest }) => {
const isCompact = variant === 'compact';
const { cx, styles, theme } = useStyles();
return (
<ConfigProvider
theme={{
components: {
Menu: {
controlHeightLG: 36,
iconMarginInlineEnd: 8,
iconSize: 16,
itemBorderRadius: theme.borderRadius,
itemColor: selectable ? theme.colorTextSecondary : theme.colorText,
itemHoverBg: theme.colorFillTertiary,
itemMarginBlock: isCompact ? 0 : 4,
itemMarginInline: isCompact ? 0 : 4,
itemSelectedBg: theme.colorFillSecondary,
paddingXS: -8,
},
},
}}
>
<AntdMenu
className={cx(styles.menu, isCompact && styles.compact, className)}
mode="vertical"
selectable={selectable}
{...rest}
/>
</ConfigProvider>
);
});
export default Menu;

View File

@ -15,7 +15,7 @@ const MobileSidebar = memo<SidebarProps>(({ tab, setTab }) => {
<Segmented
block
onChange={setTab as any}
options={items.map(({ value, label }) => ({ label, value }))}
options={items.map(({ key, label }) => ({ label, value: key }))}
value={tab}
/>
);

View File

@ -0,0 +1,56 @@
import { createStyles } from 'antd-style';
import { ReactNode } from 'react';
import { Flexbox, FlexboxProps } from 'react-layout-kit';
import BrandWatermark from './BrandWatermark';
const useStyles = createStyles(({ token, css }) => ({
container: css`
padding: 24px 12px 16px;
background: ${token.colorBgContainer};
border-inline-end: 1px solid ${token.colorBorder};
`,
desc: css`
line-height: 1.4;
color: ${token.colorTextDescription};
`,
header: css`
padding: 0 0.75rem;
`,
logo: css`
fill: ${token.colorText};
`,
title: css`
margin: 0;
font-size: 26px;
font-weight: 600;
line-height: 1.3;
`,
}));
interface SidebarLayoutProps extends FlexboxProps {
desc?: ReactNode;
title?: string;
}
const SidebarLayout = ({ children, className, title, desc, ...rest }: SidebarLayoutProps) => {
const { cx, styles } = useStyles();
return (
<Flexbox
className={cx(styles.container, className)}
flex={'none'}
gap={20}
width={280}
{...rest}
>
<Flexbox className={styles.header} gap={4}>
<h1 className={styles.title}>{title}</h1>
{desc && <p className={styles.desc}>{desc}</p>}
</Flexbox>
{children}
<BrandWatermark paddingInline={12} />
</Flexbox>
);
};
export default SidebarLayout;

View File

@ -1,9 +1,11 @@
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import { useTabItems } from '@/features/Setting/Sidebar/useTabItems';
import Item from './Item';
import VersionTag from '../../../components/VersionTag';
import Menu from './Menu';
import SidebarLayout from './SidebarLayout';
import { useTabItems } from './useTabItems';
export enum SettingsTabs {
Appearance = 'appearance',
@ -19,19 +21,24 @@ interface SidebarProps {
const Sidebar = memo<SidebarProps>(({ tab, setTab }) => {
const items = useTabItems();
const { t } = useTranslation();
return (
<Flexbox gap={4}>
{items.map(({ value, icon, label }) => (
<Item
active={tab === value}
icon={icon}
key={value}
label={label}
onClick={() => setTab(value)}
/>
))}
</Flexbox>
<SidebarLayout
desc={
<Flexbox align={'center'} gap={4} horizontal>
{t('modal.themeSetting.desc')}
<VersionTag />
</Flexbox>
}
title={t('modal.themeSetting.title')}
>
<Menu
items={items}
onClick={({ key }) => setTab(key as SettingsTabs)}
selectable
selectedKeys={[tab as any]}
/>
</SidebarLayout>
);
});

View File

@ -1,15 +0,0 @@
import { Brush, FlaskConical, Layout, PanelRight } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { SettingsTabs } from '@/features/Setting/Sidebar/index';
export const useTabItems = () => {
const { t } = useTranslation();
return [
{ icon: Brush, label: t('setting.tab.appearance'), value: SettingsTabs.Appearance },
{ icon: Layout, label: t('setting.tab.layout'), value: SettingsTabs.Layout },
{ icon: PanelRight, label: t('setting.tab.sidebar'), value: SettingsTabs.Sidebar },
{ icon: FlaskConical, label: t('setting.tab.experimental'), value: SettingsTabs.Experimental },
];
};

View File

@ -0,0 +1,28 @@
import { Icon } from '@lobehub/ui';
import { Brush, FlaskConical, Layout, PanelRight } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { SettingsTabs } from '@/features/Setting/Sidebar/index';
export const useTabItems = () => {
const { t } = useTranslation();
return [
{
icon: <Icon icon={Brush} />,
key: SettingsTabs.Appearance,
label: t('setting.tab.appearance'),
},
{ icon: <Icon icon={Layout} />, key: SettingsTabs.Layout, label: t('setting.tab.layout') },
{
icon: <Icon icon={PanelRight} />,
key: SettingsTabs.Sidebar,
label: t('setting.tab.sidebar'),
},
{
icon: <Icon icon={FlaskConical} />,
key: SettingsTabs.Experimental,
label: t('setting.tab.experimental'),
},
];
};

View File

@ -1,13 +1,8 @@
import { ActionIcon, Modal, type ModalProps } from '@lobehub/ui';
import { useResponsive } from 'antd-style';
import { Book } from 'lucide-react';
import { Modal, type ModalProps } from '@lobehub/ui';
import { useResponsive, useTheme } from 'antd-style';
import { memo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import VersionTag from '@/components/VersionTag';
import { GITHUB_REPO_URL } from '@/const/url';
import FormAppearance from './Form/Appearance';
import FormExperimental from './Form/Experimental';
import Footer from './Form/Footer';
@ -23,7 +18,7 @@ export interface SettingProps {
const Setting = memo<SettingProps>(({ open, onCancel }) => {
const [tab, setTab] = useState<SettingsTabs>(SettingsTabs.Appearance);
const { mobile } = useResponsive();
const { t } = useTranslation();
const theme = useTheme();
const content = (
<>
@ -36,37 +31,65 @@ const Setting = memo<SettingProps>(({ open, onCancel }) => {
return (
<Modal
footer={<Footer />}
allowFullscreen={true}
footer={mobile ? <Footer /> : null}
onCancel={onCancel}
open={open}
styles={{
body: mobile ? { padding: 0 } : {},
body: {
display: 'flex',
minHeight: 'min(75vh, 750px)',
overflow: 'hidden',
padding: 0,
paddingBlock: 0,
},
content: {
background: mobile ? theme.colorBgContainer : undefined,
border: 'none',
boxShadow: `0 0 0 1px ${theme.colorBorderSecondary}`,
},
}}
title={
<Flexbox align={'center'} gap={4}>
<Flexbox align={'center'} gap={4} horizontal>
<a href={GITHUB_REPO_URL} rel="noreferrer" target="_blank">
<ActionIcon icon={Book} title="Setting Documents" />
</a>
{t('modal.themeSetting.title')}
<VersionTag />
</Flexbox>
</Flexbox>
}
width={960}
title={false}
width={1024}
>
{mobile ? (
<Flexbox>
<Flexbox
height={'100%'}
style={{ overflow: 'hidden', position: 'relative' }}
width={'100%'}
>
<div style={{ padding: 16 }}>
<MobileSidebar setTab={setTab} tab={tab} />
</div>
{content}
<Flexbox
height={'100%'}
style={{ overflowX: 'hidden', overflowY: 'auto', position: 'relative' }}
width={'100%'}
>
{content}
</Flexbox>
</Flexbox>
) : (
<Flexbox gap={16} horizontal>
<Flexbox horizontal width={'100%'}>
<Sidebar setTab={setTab} tab={tab} />
{content}
<Flexbox
align={'center'}
gap={64}
style={{
background: theme.isDarkMode ? theme.colorFillQuaternary : theme.colorBgElevated,
minHeight: '100%',
overflowX: 'hidden',
overflowY: 'auto',
paddingBlock: 40,
paddingInline: 56,
}}
width={'100%'}
>
{content}
<Flexbox width={'100%'}>
<Footer />
</Flexbox>
</Flexbox>
</Flexbox>
)}
</Modal>

View File

@ -1,6 +1,7 @@
import { Converter } from '@/scripts/formatPrompt';
import { parseFromRawInfo } from '@bluelovers/auto1111-pnginfo';
import { Converter } from '@/scripts/formatPrompt';
const formatPrompt = (prompt: string) => {
let newPrompt = prompt.replaceAll('&lt;', '<').replaceAll('&gt;', '>');
return Converter.convert(newPrompt);
@ -13,13 +14,13 @@ export const formatInfo = (info: string) => {
let {
prompt: position,
negative_prompt: negative,
...config,
...config
} = parseFromRawInfo(info, {
isIncludePrompts: true,
})
});
position = position.trim().replaceAll('<br>', '\n').replace(/[\s\r\n]+$/g, '');
negative = negative.trim().replaceAll('<br>', '\n').replace(/[\s\r\n]+$/g, '');
position = position.trim().replaceAll('<br>', '\n').replaceAll(/\s+$/g, '');
negative = negative.trim().replaceAll('<br>', '\n').replaceAll(/\s+$/g, '');
position = position ? formatPrompt(position) : '';
negative = negative ? formatPrompt(negative) : '';