mirror of
https://github.com/lobehub/sd-webui-lobe-theme.git
synced 2026-01-09 06:23:44 +08:00
✨ feat(prompt): add prompt syntax highlighting
This commit is contained in:
parent
9ed6da2554
commit
b51301df08
@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><kbd>文档目录</kbd></summary>
|
<summary><kbd>文档目录</kbd></summary>
|
||||||
@ -57,8 +57,7 @@
|
|||||||
- [x] 🖼️ 可调节画板比例,使生成图像始终置顶
|
- [x] 🖼️ 可调节画板比例,使生成图像始终置顶
|
||||||
- [x] 📱 移动端友好,针对手机屏幕完成部分优化
|
- [x] 📱 移动端友好,针对手机屏幕完成部分优化
|
||||||
- [x] 🇨🇳 支持 i18n 并欢迎提交 [PR](https://github.com/canisminor1990/sd-webui-lobe-theme/tree/main/src/i18n/lang) 贡献
|
- [x] 🇨🇳 支持 i18n 并欢迎提交 [PR](https://github.com/canisminor1990/sd-webui-lobe-theme/tree/main/src/i18n/lang) 贡献
|
||||||
- [ ] 📝 语法高亮的 Prompt 输入框
|
- [x] 📝 语法高亮的 Prompt 输入框
|
||||||
- [ ] 🆗 i18n 多语言支持
|
|
||||||
|
|
||||||
<div align="right">
|
<div align="right">
|
||||||
|
|
||||||
@ -92,7 +91,7 @@ git clone "https://github.com/canisminor1990/sd-webui-lobe-theme" extensions/lob
|
|||||||
|
|
||||||
## 🤯 使用说明
|
## 🤯 使用说明
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
#### 亮暗色主题
|
#### 亮暗色主题
|
||||||
|
|
||||||
@ -117,7 +116,7 @@ http://localhost:7860/?__theme=dark
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
#### 主体定制
|
#### 主体定制
|
||||||
|
|
||||||
@ -136,7 +135,21 @@ http://localhost:7860/?__theme=dark
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### Prompt 语法高亮
|
||||||
|
|
||||||
|
按 Stable Diffusion 语法规则,自动染色 prompt 显示
|
||||||
|
|
||||||
|
<div align="right">
|
||||||
|
|
||||||
|
[![][back-to-top]](#readme-top)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
#### 侧边栏定制
|
#### 侧边栏定制
|
||||||
|
|
||||||
@ -180,7 +193,7 @@ sd_model_checkpoint, sd_vae, CLIP_stop_at_last_layers, img2img_background_color,
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
#### 移动端适配
|
#### 移动端适配
|
||||||
|
|
||||||
|
|||||||
25
README.md
25
README.md
@ -25,7 +25,7 @@ English · [简体中文](./README-zh_CN.md) · [Changelog](./CHANGELOG.md) · [
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
> 📦 After **Version 2.0.0** Kitchen theme was renamed to **Lobe Theme**. The legacy version can be accessed at [sd-webui-kitchen-theme-legacy](https://github.com/canisminor1990/sd-webui-kitchen-theme-legacy)
|
> 📦 After **Version 2.0.0** Kitchen theme was renamed to **Lobe Theme**. The legacy version can be accessed at [sd-webui-kitchen-theme-legacy](https://github.com/canisminor1990/sd-webui-kitchen-theme-legacy)
|
||||||
|
|
||||||
@ -57,8 +57,7 @@ English · [简体中文](./README-zh_CN.md) · [Changelog](./CHANGELOG.md) · [
|
|||||||
- [x] 🖼️ Adjustable canvas ratio, ensuring that generated images are always displayed at the top
|
- [x] 🖼️ Adjustable canvas ratio, ensuring that generated images are always displayed at the top
|
||||||
- [x] 📱 Mobile-friendly, with partial optimization for mobile screens
|
- [x] 📱 Mobile-friendly, with partial optimization for mobile screens
|
||||||
- [x] 🇨🇳 Support i18n and welcome [PR](https://github.com/canisminor1990/sd-webui-lobe-theme/tree/main/src/i18n/lang) contributions
|
- [x] 🇨🇳 Support i18n and welcome [PR](https://github.com/canisminor1990/sd-webui-lobe-theme/tree/main/src/i18n/lang) contributions
|
||||||
- [ ] 📝 Syntax highlighting in the prompt input box
|
- [x] 📝 Syntax highlighting in the prompt input box
|
||||||
- [ ] 🆗 Multilingual support with i18n
|
|
||||||
|
|
||||||
<div align="right">
|
<div align="right">
|
||||||
|
|
||||||
@ -92,7 +91,7 @@ git clone "https://github.com/canisminor1990/sd-webui-lobe-theme" extensions/lob
|
|||||||
|
|
||||||
## 🤯 Usage
|
## 🤯 Usage
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
#### Light and Dark Themes
|
#### Light and Dark Themes
|
||||||
|
|
||||||
@ -117,7 +116,7 @@ http://localhost:7860/?__theme=dark
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
#### Theme Customization
|
#### Theme Customization
|
||||||
|
|
||||||
@ -136,7 +135,19 @@ http://localhost:7860/?__theme=dark
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
#### Prompt Syntax Highlighting
|
||||||
|
|
||||||
|
Automatically colorize prompt display according to the Stable Diffusion syntax rules
|
||||||
|
|
||||||
|
<div align="right">
|
||||||
|
|
||||||
|
[![][back-to-top]](#readme-top)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
#### Sidebar Customization
|
#### Sidebar Customization
|
||||||
|
|
||||||
@ -180,7 +191,7 @@ sd_model_checkpoint, sd_vae, CLIP_stop_at_last_layers, img2img_background_color,
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
#### Mobile Adaptation
|
#### Mobile Adaptation
|
||||||
|
|
||||||
|
|||||||
BIN
docs/feat_highlight.webp
Normal file
BIN
docs/feat_highlight.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 98 KiB |
File diff suppressed because one or more lines are too long
@ -58,7 +58,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ant-design/icons": "^5",
|
|
||||||
"@babel/plugin-syntax-import-assertions": "^7",
|
"@babel/plugin-syntax-import-assertions": "^7",
|
||||||
"@commitlint/cli": "^17",
|
"@commitlint/cli": "^17",
|
||||||
"@giscus/react": "^2",
|
"@giscus/react": "^2",
|
||||||
@ -75,21 +74,16 @@
|
|||||||
"ahooks": "^3",
|
"ahooks": "^3",
|
||||||
"antd": "^5",
|
"antd": "^5",
|
||||||
"antd-style": "latest",
|
"antd-style": "latest",
|
||||||
"babel-plugin-styled-components": "^2",
|
|
||||||
"browserslist": "^4",
|
|
||||||
"commitlint": "^17",
|
"commitlint": "^17",
|
||||||
"concurrently": "^8",
|
"concurrently": "^8",
|
||||||
"css-minimizer-webpack-plugin": "^5",
|
|
||||||
"eslint": "^8",
|
"eslint": "^8",
|
||||||
"fast-deep-equal": "^3",
|
"fast-deep-equal": "^3",
|
||||||
"husky": "^8",
|
"husky": "^8",
|
||||||
"i18next": "^23",
|
"i18next": "^23",
|
||||||
"lightningcss": "^1",
|
|
||||||
"lint-staged": "^13",
|
"lint-staged": "^13",
|
||||||
"lodash-es": "^4",
|
"lodash-es": "^4",
|
||||||
"lucide-react": "latest",
|
"lucide-react": "latest",
|
||||||
"lucide-static": "latest",
|
"lucide-static": "latest",
|
||||||
"object-to-css-variables": "^0",
|
|
||||||
"polished": "^4",
|
"polished": "^4",
|
||||||
"prettier": "^2",
|
"prettier": "^2",
|
||||||
"query-string": "^8",
|
"query-string": "^8",
|
||||||
@ -99,11 +93,13 @@
|
|||||||
"react-i18next": "^13",
|
"react-i18next": "^13",
|
||||||
"react-layout-kit": "^1",
|
"react-layout-kit": "^1",
|
||||||
"react-rnd": "^10",
|
"react-rnd": "^10",
|
||||||
|
"react-simple-code-editor": "^0",
|
||||||
"react-tag-input": "^6",
|
"react-tag-input": "^6",
|
||||||
"remark": "^14",
|
"remark": "^14",
|
||||||
"remark-cli": "^11",
|
"remark-cli": "^11",
|
||||||
"rollup-plugin-terser": "^7",
|
"rollup-plugin-terser": "^7",
|
||||||
"semantic-release": "^21",
|
"semantic-release": "^21",
|
||||||
|
"shiki-es": "^0",
|
||||||
"styled-components": "latest",
|
"styled-components": "latest",
|
||||||
"stylelint": "^15",
|
"stylelint": "^15",
|
||||||
"typescript": "^5",
|
"typescript": "^5",
|
||||||
|
|||||||
@ -19,8 +19,8 @@ const App = memo(() => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onInit();
|
|
||||||
console.time('🤯 Lobe Theme loading');
|
console.time('🤯 Lobe Theme loading');
|
||||||
|
onInit();
|
||||||
onUiLoaded(() => {
|
onUiLoaded(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
console.timeEnd('🤯 Lobe Theme loading');
|
console.timeEnd('🤯 Lobe Theme loading');
|
||||||
@ -64,6 +64,7 @@ const App = memo(() => {
|
|||||||
<meta content="#000000" name="msapplication-TileColor" />
|
<meta content="#000000" name="msapplication-TileColor" />
|
||||||
<meta content="#000000" name="theme-color" />
|
<meta content="#000000" name="theme-color" />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
{!storeLoading && <Layout>{loading ? <Loading /> : <Index />}</Layout>}
|
{!storeLoading && <Layout>{loading ? <Loading /> : <Index />}</Layout>}
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
|
|||||||
48
src/components/SyntaxHighlighter/index.tsx
Normal file
48
src/components/SyntaxHighlighter/index.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { type HighlighterProps, Icon } from '@lobehub/ui';
|
||||||
|
import { Loader2 } from 'lucide-react';
|
||||||
|
import { memo, useEffect } from 'react';
|
||||||
|
import { Center } from 'react-layout-kit';
|
||||||
|
import { shallow } from 'zustand/shallow';
|
||||||
|
|
||||||
|
import { useHighlight } from '@/hooks/useHighlight';
|
||||||
|
|
||||||
|
import { useStyles } from './style';
|
||||||
|
|
||||||
|
export type SyntaxHighlighterProps = Pick<HighlighterProps, 'children' | 'theme'>;
|
||||||
|
|
||||||
|
const SyntaxHighlighter = memo<SyntaxHighlighterProps>(({ theme, children }) => {
|
||||||
|
const { styles } = useStyles();
|
||||||
|
const [codeToHtml, isLoading] = useHighlight((s) => [s.codeToHtml, !s.highlighter], shallow);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
useHighlight.getState().initHighlighter();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isLoading ? (
|
||||||
|
<div className={styles.shiki}>
|
||||||
|
<pre>
|
||||||
|
<code>{children}</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={styles.shiki}
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: codeToHtml(children, 'prompt', theme === 'dark') || '',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isLoading && (
|
||||||
|
<Center className={styles.loading} gap={8} horizontal>
|
||||||
|
<Icon icon={Loader2} spin />
|
||||||
|
Highlighting...
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default SyntaxHighlighter;
|
||||||
50
src/components/SyntaxHighlighter/prompt.tmLanguage.json
Normal file
50
src/components/SyntaxHighlighter/prompt.tmLanguage.json
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
|
||||||
|
"fileTypes": ["prompt"],
|
||||||
|
"name": "prompt",
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"match": "[,]",
|
||||||
|
"name": "comma"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": "[:|]",
|
||||||
|
"name": "func"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": "AND",
|
||||||
|
"name": "and"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": "<([^:]+):([^:]+):([^>]+)>",
|
||||||
|
"captures": {
|
||||||
|
"0": {
|
||||||
|
"name": "model-bracket"
|
||||||
|
},
|
||||||
|
"1": {
|
||||||
|
"name": "model-type"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"name": "model-name"
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
"name": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": "[<|>]",
|
||||||
|
"name": "model-bracket"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": "[(|)|\\[|\\]|{|}]",
|
||||||
|
"name": "bracket"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": "\\d+(\\.\\d+)?",
|
||||||
|
"name": "number"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"scopeName": "source.prompt"
|
||||||
|
}
|
||||||
72
src/components/SyntaxHighlighter/promptTheme.ts
Normal file
72
src/components/SyntaxHighlighter/promptTheme.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { colors as colorScales } from '@lobehub/ui';
|
||||||
|
import { ThemeAppearance } from 'antd-style';
|
||||||
|
|
||||||
|
export const themeConfig: any = (isDarkMode: ThemeAppearance) => {
|
||||||
|
const type = isDarkMode ? 'dark' : 'light';
|
||||||
|
|
||||||
|
const colorTextTertiary = isDarkMode ? colorScales.gray[type][6] : colorScales.gray[type][7];
|
||||||
|
const colorOrange = isDarkMode ? colorScales.gold[type][9] : colorScales.orange[type][9];
|
||||||
|
const colorGreen = isDarkMode ? colorScales.lime[type][9] : colorScales.green[type][10];
|
||||||
|
const colorBlue = isDarkMode ? colorScales.blue[type][9] : colorScales.geekblue[type][8];
|
||||||
|
const colorPurple = isDarkMode ? colorScales.purple[type][10] : colorScales.purple[type][9];
|
||||||
|
return {
|
||||||
|
colors: {
|
||||||
|
'editor.foreground': colorGreen,
|
||||||
|
},
|
||||||
|
name: type,
|
||||||
|
tokenColors: [
|
||||||
|
{
|
||||||
|
scope: 'comma',
|
||||||
|
settings: {
|
||||||
|
foreground: colorTextTertiary,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scope: 'func',
|
||||||
|
settings: {
|
||||||
|
foreground: colorBlue,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scope: 'and',
|
||||||
|
settings: {
|
||||||
|
fontStyle: 'bold',
|
||||||
|
foreground: colorBlue,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scope: 'bracket',
|
||||||
|
settings: {
|
||||||
|
foreground: colorBlue,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scope: 'model-type',
|
||||||
|
settings: {
|
||||||
|
fontStyle: 'italic',
|
||||||
|
foreground: colorOrange,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scope: 'model-name',
|
||||||
|
settings: {
|
||||||
|
fontStyle: 'bold',
|
||||||
|
foreground: colorOrange,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scope: 'model-bracket',
|
||||||
|
settings: {
|
||||||
|
foreground: colorOrange,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scope: 'number',
|
||||||
|
settings: {
|
||||||
|
foreground: colorPurple,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type,
|
||||||
|
};
|
||||||
|
};
|
||||||
45
src/components/SyntaxHighlighter/style.ts
Normal file
45
src/components/SyntaxHighlighter/style.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { createStyles } from 'antd-style';
|
||||||
|
|
||||||
|
export const useStyles = createStyles(({ css, token, cx, prefixCls, stylish }) => {
|
||||||
|
const prefix = `${prefixCls}-highlighter`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
loading: cx(
|
||||||
|
stylish.blur,
|
||||||
|
css`
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
height: 34px;
|
||||||
|
padding: 0 8px;
|
||||||
|
|
||||||
|
font-family: ${token.fontFamilyCode};
|
||||||
|
color: ${token.colorTextTertiary};
|
||||||
|
|
||||||
|
border-radius: ${token.borderRadius};
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
prism: css`
|
||||||
|
pre {
|
||||||
|
overflow: auto;
|
||||||
|
font-family: ${token.fontFamilyCode} !important;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
|
||||||
|
shiki: cx(
|
||||||
|
`${prefix}-shiki`,
|
||||||
|
css`
|
||||||
|
.shiki {
|
||||||
|
overflow-x: auto;
|
||||||
|
background: none !important;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
@ -5,3 +5,4 @@ export { default as SidebarBody } from './Sidebar/SidebarBody';
|
|||||||
export { default as SidebarContainer } from './Sidebar/SidebarContainer';
|
export { default as SidebarContainer } from './Sidebar/SidebarContainer';
|
||||||
export { default as SidebarFooter } from './Sidebar/SidebarFooter';
|
export { default as SidebarFooter } from './Sidebar/SidebarFooter';
|
||||||
export { default as SidebarHeader, type SidebarHeaderProps } from './Sidebar/SidebarHeader';
|
export { default as SidebarHeader, type SidebarHeaderProps } from './Sidebar/SidebarHeader';
|
||||||
|
export { default as SyntaxHighlighter } from './SyntaxHighlighter';
|
||||||
|
|||||||
37
src/hooks/useExternalTextareaObserver.ts
Normal file
37
src/hooks/useExternalTextareaObserver.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export const useExternalTextareaObserver = (textareaSelector: string) => {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
observer.disconnect();
|
||||||
|
};
|
||||||
|
}, [textareaSelector]);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
};
|
||||||
48
src/hooks/useHighlight.ts
Normal file
48
src/hooks/useHighlight.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// @ts-ignore
|
||||||
|
import { type Highlighter, getHighlighter } from 'shiki-es';
|
||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
import grammar from '@/components/SyntaxHighlighter/prompt.tmLanguage.json';
|
||||||
|
import { themeConfig } from '@/components/SyntaxHighlighter/promptTheme';
|
||||||
|
|
||||||
|
interface Store {
|
||||||
|
codeToHtml: (text: string, language: string, isDarkMode: boolean) => string;
|
||||||
|
highlighter?: Highlighter;
|
||||||
|
initHighlighter: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useHighlight = create<Store>((set, get) => ({
|
||||||
|
codeToHtml: (text, language = 'prompt', isDarkMode) => {
|
||||||
|
const { highlighter } = get();
|
||||||
|
|
||||||
|
if (!highlighter) return '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
return highlighter?.codeToHtml(text, {
|
||||||
|
lang: language,
|
||||||
|
theme: isDarkMode ? 'dark' : 'light',
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
highlighter: undefined,
|
||||||
|
|
||||||
|
initHighlighter: async() => {
|
||||||
|
if (!get().highlighter) {
|
||||||
|
const highlighter = await getHighlighter({
|
||||||
|
langs: [
|
||||||
|
{
|
||||||
|
aliases: ['prompt'],
|
||||||
|
grammar: grammar,
|
||||||
|
id: 'prompt',
|
||||||
|
scopeName: 'source.prompt',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
themes: [themeConfig(true), themeConfig(false)],
|
||||||
|
});
|
||||||
|
|
||||||
|
set({ highlighter });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}));
|
||||||
@ -1,14 +1,14 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
function checkIsDarkMode() {
|
const checkIsDarkMode = () => {
|
||||||
try {
|
try {
|
||||||
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export function useIsDarkMode() {
|
export const useIsDarkMode = () => {
|
||||||
const [isDarkMode, setIsDarkMode] = useState(checkIsDarkMode());
|
const [isDarkMode, setIsDarkMode] = useState(checkIsDarkMode());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -26,4 +26,4 @@ export function useIsDarkMode() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return isDarkMode;
|
return isDarkMode;
|
||||||
}
|
};
|
||||||
|
|||||||
@ -62,6 +62,9 @@ const translation = {
|
|||||||
settingPromptDisplayModeDesc: 'Fixed height or auto height with draggable resize support',
|
settingPromptDisplayModeDesc: 'Fixed height or auto height with draggable resize support',
|
||||||
settingPromptEditor: 'Prompt Editor',
|
settingPromptEditor: 'Prompt Editor',
|
||||||
settingPromptEditorDesc: 'Provide a simple prompt editor at the top of the quick setting sidebar',
|
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',
|
settingQuickSettingSidebarDefaultExpand: 'Default Expand',
|
||||||
settingQuickSettingSidebarDefaultExpandDesc:
|
settingQuickSettingSidebarDefaultExpandDesc:
|
||||||
'Whether to expand the sidebar by default when starting',
|
'Whether to expand the sidebar by default when starting',
|
||||||
|
|||||||
@ -62,6 +62,9 @@ const translation: Translation = {
|
|||||||
settingPromptDisplayModeDesc: '固定の高さまたはドラッグリサイズをサポートする自動の高さ',
|
settingPromptDisplayModeDesc: '固定の高さまたはドラッグリサイズをサポートする自動の高さ',
|
||||||
settingPromptEditor: 'プロンプトエディタ',
|
settingPromptEditor: 'プロンプトエディタ',
|
||||||
settingPromptEditorDesc: 'クイック設定サイドバーの上部に簡単なプロンプトエディタを提供します',
|
settingPromptEditorDesc: 'クイック設定サイドバーの上部に簡単なプロンプトエディタを提供します',
|
||||||
|
settingPromptHighlight: 'Promptのシンタックスハイライト',
|
||||||
|
settingPromptHighlightDesc:
|
||||||
|
'Stable Diffusionのシンタックスルールに基づいて、promptの表示を自動的にハイライトします',
|
||||||
settingQuickSettingSidebarDefaultExpand: 'デフォルトで展開',
|
settingQuickSettingSidebarDefaultExpand: 'デフォルトで展開',
|
||||||
settingQuickSettingSidebarDefaultExpandDesc: '起動時にサイドバーをデフォルトで展開しますか?',
|
settingQuickSettingSidebarDefaultExpandDesc: '起動時にサイドバーをデフォルトで展開しますか?',
|
||||||
settingQuickSettingSidebarDefaultWidth: 'デフォルト幅',
|
settingQuickSettingSidebarDefaultWidth: 'デフォルト幅',
|
||||||
|
|||||||
@ -61,6 +61,9 @@ const translation: Translation = {
|
|||||||
settingPromptDisplayModeDesc: '고정 높이 또는 자동 높이 및 드래그 조절 지원',
|
settingPromptDisplayModeDesc: '고정 높이 또는 자동 높이 및 드래그 조절 지원',
|
||||||
settingPromptEditor: '프롬프트 편집기',
|
settingPromptEditor: '프롬프트 편집기',
|
||||||
settingPromptEditorDesc: '빠른 설정 사이드바 상단에 간단한 프롬프트 편집기 제공',
|
settingPromptEditorDesc: '빠른 설정 사이드바 상단에 간단한 프롬프트 편집기 제공',
|
||||||
|
settingPromptHighlight: 'Prompt 구문 강조',
|
||||||
|
settingPromptHighlightDesc:
|
||||||
|
'Stable Diffusion 구문 규칙에 따라 자동으로 prompt를 강조하여 표시합니다',
|
||||||
settingQuickSettingSidebarDefaultExpand: '기본 확장',
|
settingQuickSettingSidebarDefaultExpand: '기본 확장',
|
||||||
settingQuickSettingSidebarDefaultExpandDesc: '시작시 사이드바 기본 확장 여부',
|
settingQuickSettingSidebarDefaultExpandDesc: '시작시 사이드바 기본 확장 여부',
|
||||||
settingQuickSettingSidebarDefaultWidth: '기본 너비',
|
settingQuickSettingSidebarDefaultWidth: '기본 너비',
|
||||||
|
|||||||
@ -59,6 +59,8 @@ const translation: Translation = {
|
|||||||
settingPromptDisplayModeDesc: '固定高度或自动高度并支持拖拽拉伸',
|
settingPromptDisplayModeDesc: '固定高度或自动高度并支持拖拽拉伸',
|
||||||
settingPromptEditor: '提示词编辑器',
|
settingPromptEditor: '提示词编辑器',
|
||||||
settingPromptEditorDesc: '提供简易的提示词编辑器位于快捷设置侧边栏顶部',
|
settingPromptEditorDesc: '提供简易的提示词编辑器位于快捷设置侧边栏顶部',
|
||||||
|
settingPromptHighlight: 'Prompt 语法高亮',
|
||||||
|
settingPromptHighlightDesc: '按 Stable Diffusion 语法规则,自动染色 prompt 显示',
|
||||||
settingQuickSettingSidebarDefaultExpand: '默认展开',
|
settingQuickSettingSidebarDefaultExpand: '默认展开',
|
||||||
settingQuickSettingSidebarDefaultExpandDesc: '是否在启动时将侧边栏默认展开',
|
settingQuickSettingSidebarDefaultExpandDesc: '是否在启动时将侧边栏默认展开',
|
||||||
settingQuickSettingSidebarDefaultWidth: '默认宽度',
|
settingQuickSettingSidebarDefaultWidth: '默认宽度',
|
||||||
|
|||||||
@ -59,6 +59,8 @@ const translation: Translation = {
|
|||||||
settingPromptDisplayModeDesc: '固定高度或自動高度並支持拖拽拉伸',
|
settingPromptDisplayModeDesc: '固定高度或自動高度並支持拖拽拉伸',
|
||||||
settingPromptEditor: '提示詞編輯器',
|
settingPromptEditor: '提示詞編輯器',
|
||||||
settingPromptEditorDesc: '提供簡易的提示詞編輯器位於快捷設置側邊欄頂部',
|
settingPromptEditorDesc: '提供簡易的提示詞編輯器位於快捷設置側邊欄頂部',
|
||||||
|
settingPromptHighlight: 'Prompt 語法高亮',
|
||||||
|
settingPromptHighlightDesc: '按照 Stable Diffusion 語法規則,自動著色 prompt 顯示',
|
||||||
settingQuickSettingSidebarDefaultExpand: '默認展開',
|
settingQuickSettingSidebarDefaultExpand: '默認展開',
|
||||||
settingQuickSettingSidebarDefaultExpandDesc: '是否在啟動時將側邊欄默認展開',
|
settingQuickSettingSidebarDefaultExpandDesc: '是否在啟動時將側邊欄默認展開',
|
||||||
settingQuickSettingSidebarDefaultWidth: '默認寬度',
|
settingQuickSettingSidebarDefaultWidth: '默認寬度',
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import { shallow } from 'zustand/shallow';
|
|||||||
|
|
||||||
import { useIsDarkMode } from '@/hooks/useIsDarkMode';
|
import { useIsDarkMode } from '@/hooks/useIsDarkMode';
|
||||||
import { useAppStore } from '@/store';
|
import { useAppStore } from '@/store';
|
||||||
import GlobalStyle from '@/styles/index';
|
|
||||||
import { kitchenNeutral, kitchenPrimary } from '@/styles/kitchenColors';
|
import { kitchenNeutral, kitchenPrimary } from '@/styles/kitchenColors';
|
||||||
import { neutralColorScales } from '@/styles/neutralColors';
|
import { neutralColorScales } from '@/styles/neutralColors';
|
||||||
|
|
||||||
@ -67,7 +66,6 @@ const Layout = memo<DivProps>(({ children }) => {
|
|||||||
['https://npm.elemecdn.com/normalize.css/normalize.css']
|
['https://npm.elemecdn.com/normalize.css/normalize.css']
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<GlobalStyle />
|
|
||||||
{children}
|
{children}
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
)
|
)
|
||||||
|
|||||||
66
src/modules/PromptHighlight/App.tsx
Normal file
66
src/modules/PromptHighlight/App.tsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import Editor from 'react-simple-code-editor';
|
||||||
|
import { shallow } from 'zustand/shallow';
|
||||||
|
|
||||||
|
import { SyntaxHighlighter } from '@/components';
|
||||||
|
import { useExternalTextareaObserver } from '@/hooks/useExternalTextareaObserver';
|
||||||
|
import { useAppStore } from '@/store';
|
||||||
|
|
||||||
|
import { useStyles } from './style';
|
||||||
|
|
||||||
|
interface AppProps {
|
||||||
|
parentId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const App = memo<AppProps>(({ parentId }) => {
|
||||||
|
const reference = useRef(null);
|
||||||
|
const [prompt, setPrompt] = useState<string>('');
|
||||||
|
const themeMode = useAppStore((s) => s.themeMode, shallow);
|
||||||
|
const { styles, cx } = useStyles();
|
||||||
|
const nativeTextareaValue = useExternalTextareaObserver(`${parentId} label textarea`);
|
||||||
|
|
||||||
|
const nativeTextarea = useMemo(
|
||||||
|
() => gradioApp().querySelector(`${parentId} label textarea`) as HTMLTextAreaElement,
|
||||||
|
[parentId],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handlePromptChange = useCallback((event: any) => {
|
||||||
|
setPrompt(event.target.value);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
nativeTextarea.style.display = 'none';
|
||||||
|
nativeTextarea.addEventListener('change', handlePromptChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
nativeTextarea.removeEventListener('change', handlePromptChange);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPrompt(nativeTextareaValue);
|
||||||
|
}, [nativeTextareaValue]);
|
||||||
|
|
||||||
|
const onBlur = useCallback(() => {
|
||||||
|
nativeTextarea.value = prompt;
|
||||||
|
|
||||||
|
const event = new Event('input');
|
||||||
|
nativeTextarea.dispatchEvent(event);
|
||||||
|
}, [prompt]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Editor
|
||||||
|
className={cx(styles.editor, 'prompt_editor')}
|
||||||
|
highlight={(code) => <SyntaxHighlighter theme={themeMode}>{code}</SyntaxHighlighter>}
|
||||||
|
onBlur={onBlur}
|
||||||
|
onValueChange={setPrompt}
|
||||||
|
padding={8}
|
||||||
|
placeholder={nativeTextarea.placeholder}
|
||||||
|
ref={reference}
|
||||||
|
textareaClassName={cx(styles.textarea)}
|
||||||
|
value={prompt}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
||||||
26
src/modules/PromptHighlight/index.tsx
Normal file
26
src/modules/PromptHighlight/index.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { StrictMode, Suspense } from 'react';
|
||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
|
import Layout from '@/layouts';
|
||||||
|
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
export const PromptHighlight = (parentId: string, containerId: string) => {
|
||||||
|
const settingsDiv = document.createElement('div') as HTMLDivElement;
|
||||||
|
settingsDiv.id = containerId.replace('#', '');
|
||||||
|
|
||||||
|
(gradioApp().querySelector(parentId) as HTMLDivElement).insertBefore(
|
||||||
|
settingsDiv,
|
||||||
|
(gradioApp().querySelector(parentId) as HTMLDivElement).firstChild,
|
||||||
|
);
|
||||||
|
|
||||||
|
createRoot(settingsDiv).render(
|
||||||
|
<StrictMode>
|
||||||
|
<Suspense fallback="loading...">
|
||||||
|
<Layout>
|
||||||
|
<App parentId={parentId} />
|
||||||
|
</Layout>
|
||||||
|
</Suspense>
|
||||||
|
</StrictMode>,
|
||||||
|
);
|
||||||
|
};
|
||||||
62
src/modules/PromptHighlight/style.ts
Normal file
62
src/modules/PromptHighlight/style.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { createStyles } from 'antd-style';
|
||||||
|
|
||||||
|
export const useStyles = createStyles(({ css, token }) => ({
|
||||||
|
container: css`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100px;
|
||||||
|
max-height: 800px;
|
||||||
|
`,
|
||||||
|
editor: css`
|
||||||
|
resize: vertical;
|
||||||
|
|
||||||
|
font-family: ${token.fontFamilyCode} !important;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 18.2px;
|
||||||
|
|
||||||
|
background: ${token.colorFillTertiary};
|
||||||
|
border: 1px solid ${token.colorBorderSecondary};
|
||||||
|
border-radius: ${token.borderRadius}px;
|
||||||
|
|
||||||
|
&:focus,
|
||||||
|
&:active,
|
||||||
|
&:hover {
|
||||||
|
border: 1px solid ${token.colorBorder};
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
word-wrap: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
handle: css`
|
||||||
|
cursor: row-resize;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background-color: transparent;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: ${token.colorPrimary};
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
textarea: css`
|
||||||
|
height: 100% !important;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: ${token.colorTextQuaternary};
|
||||||
|
}
|
||||||
|
|
||||||
|
&::selection {
|
||||||
|
color: #000;
|
||||||
|
background: ${token.yellow3A};
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border: none !important;
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
}));
|
||||||
@ -272,6 +272,7 @@ export const useStyles = createStyles(
|
|||||||
}
|
}
|
||||||
|
|
||||||
.block.token-counter {
|
.block.token-counter {
|
||||||
|
top: -12px;
|
||||||
right: 4px;
|
right: 4px;
|
||||||
scale: 0.8;
|
scale: 0.8;
|
||||||
background: ${token.colorBgContainer} !important;
|
background: ${token.colorBgContainer} !important;
|
||||||
@ -286,6 +287,16 @@ export const useStyles = createStyles(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#lobe_txt2img_prompt .prompt_editor {
|
||||||
|
min-height: ${TEXT2IMG_PROMPT_HEIGHT}px;
|
||||||
|
max-height: ${isPromptResizable ? 'unset' : `${TEXT2IMG_PROMPT_HEIGHT}px`};
|
||||||
|
}
|
||||||
|
|
||||||
|
#lobe_img2img_prompt .prompt_editor {
|
||||||
|
min-height: ${IMG2IMG_PROMPT_HEIGHT}px;
|
||||||
|
max-height: ${isPromptResizable ? 'unset' : `${IMG2IMG_PROMPT_HEIGHT}px`};
|
||||||
|
}
|
||||||
|
|
||||||
#text2img_prompt,
|
#text2img_prompt,
|
||||||
#text2img_neg_prompt {
|
#text2img_neg_prompt {
|
||||||
textarea {
|
textarea {
|
||||||
|
|||||||
@ -5,8 +5,10 @@ import { memo, useEffect } from 'react';
|
|||||||
import { shallow } from 'zustand/shallow';
|
import { shallow } from 'zustand/shallow';
|
||||||
|
|
||||||
import '@/i18n/config';
|
import '@/i18n/config';
|
||||||
|
import { PromptHighlight } from '@/modules/PromptHighlight';
|
||||||
import replaceIcon from '@/script/replaceIcon';
|
import replaceIcon from '@/script/replaceIcon';
|
||||||
import { useAppStore } from '@/store';
|
import { useAppStore } from '@/store';
|
||||||
|
import GlobalStyle from '@/styles';
|
||||||
|
|
||||||
import Content from './Content';
|
import Content from './Content';
|
||||||
import ExtraNetworkSidebar from './ExtraNetworkSidebar';
|
import ExtraNetworkSidebar from './ExtraNetworkSidebar';
|
||||||
@ -28,11 +30,16 @@ const Index = memo(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (setting.enableHighlight) {
|
||||||
|
PromptHighlight('#txt2img_prompt', '#lobe_txt2img_prompt');
|
||||||
|
PromptHighlight('#img2img_prompt', '#lobe_img2img_prompt');
|
||||||
|
}
|
||||||
if (setting.svgIcon) replaceIcon();
|
if (setting.svgIcon) replaceIcon();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<GlobalStyle />
|
||||||
<LayoutHeader headerHeight={HEADER_HEIGHT}>
|
<LayoutHeader headerHeight={HEADER_HEIGHT}>
|
||||||
<Header />
|
<Header />
|
||||||
</LayoutHeader>
|
</LayoutHeader>
|
||||||
|
|||||||
@ -165,6 +165,15 @@ const SettingForm = memo(() => {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
<FormItem
|
||||||
|
desc={t('settingPromptHighlightDesc')}
|
||||||
|
divider
|
||||||
|
label={t('settingPromptHighlight')}
|
||||||
|
name="enableHighlight"
|
||||||
|
valuePropName="checked"
|
||||||
|
>
|
||||||
|
<Switch />
|
||||||
|
</FormItem>
|
||||||
<FormItem
|
<FormItem
|
||||||
desc={t('settingPromptEditorDesc')}
|
desc={t('settingPromptEditorDesc')}
|
||||||
divider
|
divider
|
||||||
|
|||||||
@ -24,6 +24,7 @@ export type NeutralColor = 'mauve' | 'slate' | 'sage' | 'olive' | 'sand' | 'kitc
|
|||||||
|
|
||||||
export interface WebuiSetting {
|
export interface WebuiSetting {
|
||||||
enableExtraNetworkSidebar: boolean;
|
enableExtraNetworkSidebar: boolean;
|
||||||
|
enableHighlight: boolean;
|
||||||
enableSidebar: boolean;
|
enableSidebar: boolean;
|
||||||
enableWebFont: boolean;
|
enableWebFont: boolean;
|
||||||
extraNetworkCardSize: number;
|
extraNetworkCardSize: number;
|
||||||
@ -49,6 +50,7 @@ export interface WebuiSetting {
|
|||||||
|
|
||||||
export const defaultSetting: WebuiSetting = {
|
export const defaultSetting: WebuiSetting = {
|
||||||
enableExtraNetworkSidebar: true,
|
enableExtraNetworkSidebar: true,
|
||||||
|
enableHighlight: true,
|
||||||
enableSidebar: true,
|
enableSidebar: true,
|
||||||
enableWebFont: true,
|
enableWebFont: true,
|
||||||
extraNetworkCardSize: 86,
|
extraNetworkCardSize: 86,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user