mirror of
https://github.com/lobehub/sd-webui-lobe-theme.git
synced 2026-01-09 06:23:44 +08:00
✨ feat: Add new prompt editor
This commit is contained in:
parent
d2ec3745f0
commit
03e67ba5b8
1
.gitignore
vendored
1
.gitignore
vendored
@ -43,3 +43,4 @@ test-output
|
||||
__pycache__
|
||||
/lobe_theme_config.json
|
||||
bun.lockb
|
||||
.env
|
||||
|
||||
5914
data/prompt.json
Normal file
5914
data/prompt.json
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -29,6 +29,11 @@
|
||||
}
|
||||
},
|
||||
"prompt": {
|
||||
"area": {
|
||||
"object": "Object Selection",
|
||||
"attribute": "Attribute Selection",
|
||||
"tag": "Tag Selection"
|
||||
},
|
||||
"load": "Load Prompt",
|
||||
"set": "Set Prompt",
|
||||
"negative": "Negative",
|
||||
|
||||
@ -29,6 +29,11 @@
|
||||
}
|
||||
},
|
||||
"prompt": {
|
||||
"area": {
|
||||
"object": "对象选择区",
|
||||
"attribute": "属性选择区",
|
||||
"tag": "标签选择区"
|
||||
},
|
||||
"load": "加载提示",
|
||||
"set": "设置提示",
|
||||
"negative": "否定",
|
||||
|
||||
@ -5,12 +5,14 @@ from fastapi import FastAPI, Response, Request
|
||||
|
||||
from scripts.lib.config import LobeConfig
|
||||
from scripts.lib.package import LobePackage
|
||||
from scripts.lib.prompt import LobePrompt
|
||||
from scripts.lib.locale import LobeLocale
|
||||
from scripts.lib.lobe_log import LobeLog
|
||||
|
||||
class LobeApi:
|
||||
def __init__(self, config: LobeConfig, package: LobePackage, locale: LobeLocale):
|
||||
def __init__(self, config: LobeConfig, package: LobePackage, prompt:LobePrompt, locale: LobeLocale):
|
||||
self.package = package
|
||||
self.prompt = prompt
|
||||
self.config = config
|
||||
self.locale = locale
|
||||
pass
|
||||
@ -25,6 +27,14 @@ class LobeApi:
|
||||
return Response(content=self.package.json(), media_type="application/json", status_code=404)
|
||||
return Response(content=self.package.json(), media_type="application/json", status_code=200)
|
||||
|
||||
@app.get("/lobe/prompt")
|
||||
async def lobe_prompt_get():
|
||||
LobeLog.debug("lobe_prompt_get")
|
||||
|
||||
if self.prompt.is_empty():
|
||||
return Response(content=self.prompt.json(), media_type="application/json", status_code=404)
|
||||
return Response(content=self.prompt.json(), media_type="application/json", status_code=200)
|
||||
|
||||
@app.get("/lobe/locales/{lng}")
|
||||
async def lobe_locale_get(lng: str):
|
||||
LobeLog.debug(f"lobe_locale_get: {lng}")
|
||||
|
||||
@ -10,10 +10,10 @@ class LobeLogClass:
|
||||
|
||||
def debug(self, message: str):
|
||||
if self.logging_enabled:
|
||||
print(f"[Lobe:DEBUG]: {message}")
|
||||
print(f"[DEBUG] 🤯 LobeTheme: {message}")
|
||||
|
||||
def info(self, message: str):
|
||||
print(f"[Lobe]: {message}")
|
||||
print(f"🤯 LobeTheme: {message}")
|
||||
|
||||
|
||||
LobeLog = LobeLogClass()
|
||||
|
||||
40
scripts/lib/prompt.py
Normal file
40
scripts/lib/prompt.py
Normal file
@ -0,0 +1,40 @@
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from scripts.lib.lobe_log import LobeLog
|
||||
|
||||
EXTENSION_FOLDER = Path(__file__).parent.parent.parent
|
||||
PACKAGE_FILENAME = Path(EXTENSION_FOLDER, "data/prompt.json")
|
||||
|
||||
|
||||
LobeLog.debug(f"EXTENSION_FOLDER: {EXTENSION_FOLDER}")
|
||||
LobeLog.debug(f"PACKAGE_FILENAME: {PACKAGE_FILENAME}")
|
||||
|
||||
|
||||
class LobePrompt:
|
||||
def __init__(self):
|
||||
self.prompt_file = PACKAGE_FILENAME
|
||||
self.prompt = None
|
||||
self.load_prompt()
|
||||
|
||||
def load_prompt(self):
|
||||
if os.path.exists(self.prompt_file):
|
||||
LobeLog.debug(f"Loading prompt from prompt.json")
|
||||
|
||||
with open(self.prompt_file, 'r') as f:
|
||||
self.prompt = json.load(f)
|
||||
else:
|
||||
LobeLog.debug(f"Prompt file not found")
|
||||
self.prompt = {"error": "Prompt file not found"}
|
||||
|
||||
def is_empty(self):
|
||||
return "empty" in self.prompt and self.prompt['empty']
|
||||
|
||||
def json(self):
|
||||
return json.dumps(self.prompt)
|
||||
|
||||
@staticmethod
|
||||
def default():
|
||||
# default prompt is handled from client side @see src/store/index.tsx
|
||||
return {'empty': True}
|
||||
@ -11,15 +11,17 @@ from scripts.lib.lobe_log import LobeLog
|
||||
from scripts.lib.api import LobeApi
|
||||
from scripts.lib.config import LobeConfig
|
||||
from scripts.lib.package import LobePackage
|
||||
from scripts.lib.prompt import LobePrompt
|
||||
from scripts.lib.locale import LobeLocale
|
||||
|
||||
def init_lobe(_: Any, app: FastAPI, **kwargs):
|
||||
LobeLog.info("Initializing Lobe")
|
||||
LobeLog.info("Initializing...")
|
||||
|
||||
package = LobePackage()
|
||||
prompt = LobePrompt()
|
||||
locale = LobeLocale()
|
||||
config = LobeConfig()
|
||||
api = LobeApi(config, package, locale)
|
||||
api = LobeApi(config, package, prompt, locale)
|
||||
api.create_api_route(app)
|
||||
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { consola } from 'consola';
|
||||
import { memo, useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import TagList, { PromptType, TagItem } from './TagList';
|
||||
import { useStyles } from './style';
|
||||
@ -51,24 +52,26 @@ const Prompt = memo<PromptProps>(({ type }) => {
|
||||
return (
|
||||
<div className={styles.promptView}>
|
||||
<TagList setTags={setTags} setValue={setCurrentValue} tags={tags} type={type} />
|
||||
<div className={styles.buttonGroup}>
|
||||
<Flexbox gap={8} horizontal>
|
||||
<button
|
||||
className="lg secondary gradio-button tool svelte-1ipelgc"
|
||||
className="secondary gradio-button"
|
||||
onClick={getValue}
|
||||
style={{ flex: 1, height: 36 }}
|
||||
title={t('prompt.load')}
|
||||
type="button"
|
||||
>
|
||||
🔄
|
||||
</button>
|
||||
<button
|
||||
className="lg secondary gradio-button tool svelte-1ipelgc"
|
||||
className="secondary gradio-button"
|
||||
onClick={setValue}
|
||||
style={{ flex: 1, height: 36 }}
|
||||
title={t('prompt.set')}
|
||||
type="button"
|
||||
>
|
||||
➡️
|
||||
</button>
|
||||
</div>
|
||||
</Flexbox>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
154
src/components/PromptEditor/PromptPicker/index.tsx
Normal file
154
src/components/PromptEditor/PromptPicker/index.tsx
Normal file
@ -0,0 +1,154 @@
|
||||
import { Button, Skeleton } from 'antd';
|
||||
import { consola } from 'consola';
|
||||
import { memo, useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import { TagItem } from '@/components/PromptEditor/TagList';
|
||||
import { formatPrompt } from '@/components/PromptEditor/utils';
|
||||
import { selectors, useAppStore } from '@/store';
|
||||
import { getPrompt } from '@/store/api';
|
||||
|
||||
const ID = `[id$='2img_prompt'] textarea`;
|
||||
|
||||
const PromptPicker = memo(() => {
|
||||
const { data, isLoading } = useSWR('prompt', getPrompt);
|
||||
const [tags, setTags] = useState<TagItem[]>([]);
|
||||
const [activeObject, setActiveObject] = useState<string>();
|
||||
const [activeAttribute, setActiveAttribute] = useState<string>();
|
||||
const i18n = useAppStore(selectors.currentLanguage);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const isCN = i18n === 'zh_CN' || i18n === 'zh_HK';
|
||||
|
||||
const getValue = useCallback(() => {
|
||||
try {
|
||||
const textarea = get_uiCurrentTabContent().querySelector(ID) as HTMLTextAreaElement;
|
||||
const data = formatPrompt(textarea.value);
|
||||
if (textarea) setTags(data);
|
||||
return data;
|
||||
} catch (error) {
|
||||
consola.error('🤯 [prompt]', error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const setValue = useCallback((currentTags: TagItem[]) => {
|
||||
try {
|
||||
const newValue = currentTags.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);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleTagUpdate = useCallback((tag: TagItem) => {
|
||||
let currentTags = getValue() || [];
|
||||
console.log(currentTags);
|
||||
const hasTag = currentTags.some(
|
||||
(t) => t.text.toLowerCase() === tag.text.toLowerCase() || t.id === tag.id,
|
||||
);
|
||||
if (hasTag) {
|
||||
currentTags = currentTags.filter(
|
||||
(t) => t.text.toLowerCase() !== tag.text.toLowerCase() && t.id !== tag.id,
|
||||
);
|
||||
} else {
|
||||
currentTags = [...currentTags, tag].filter(Boolean);
|
||||
}
|
||||
|
||||
setTags(currentTags);
|
||||
setValue(currentTags);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
getValue();
|
||||
if (!data || activeObject || activeAttribute) return;
|
||||
const defaultActiveObject = Object.keys(data)[0];
|
||||
setActiveObject(defaultActiveObject);
|
||||
const defaultActiveAttribute = Object.keys(data[defaultActiveObject].children)[0];
|
||||
setActiveAttribute(defaultActiveAttribute);
|
||||
}, [data, activeObject, activeAttribute]);
|
||||
|
||||
if (isLoading || !data) return <Skeleton active />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<span style={{ marginBottom: -10 }}>{t('prompt.area.object')}</span>
|
||||
<Flexbox gap={4} horizontal style={{ flexWrap: 'wrap' }}>
|
||||
{Object.entries(data).map(([key, value], index) => {
|
||||
const name = isCN ? value.langName : value.name;
|
||||
const isActive = activeObject ? activeObject === key : index === 0;
|
||||
return (
|
||||
<Button
|
||||
key={key}
|
||||
onClick={() => {
|
||||
setActiveObject(key);
|
||||
setActiveAttribute(Object.keys(data[key].children)[0]);
|
||||
}}
|
||||
size={'small'}
|
||||
style={{ flex: 1 }}
|
||||
type={isActive ? 'primary' : 'default'}
|
||||
>
|
||||
{name}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</Flexbox>
|
||||
<span style={{ marginBottom: -10 }}>{t('prompt.area.attribute')}</span>
|
||||
<Flexbox gap={4} horizontal style={{ flexWrap: 'wrap' }}>
|
||||
{activeObject &&
|
||||
Object.entries(data[activeObject].children).map(([key, value], index) => {
|
||||
const name = isCN ? value.langName : value.name;
|
||||
const isActive = activeAttribute ? activeAttribute === key : index === 0;
|
||||
return (
|
||||
<Button
|
||||
key={key}
|
||||
onClick={() => setActiveAttribute(key)}
|
||||
size={'small'}
|
||||
style={{ flex: 1 }}
|
||||
type={isActive ? 'primary' : 'default'}
|
||||
>
|
||||
{name}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</Flexbox>
|
||||
<span style={{ marginBottom: -10 }}>{t('prompt.area.tag')}</span>
|
||||
<Flexbox gap={4} horizontal style={{ flexWrap: 'wrap' }}>
|
||||
{activeObject &&
|
||||
activeAttribute &&
|
||||
Object.entries(data[activeObject].children[activeAttribute].children).map(
|
||||
([key, value]) => {
|
||||
const isActive = tags.some(
|
||||
(tag) => tag.text.toLowerCase() === value.name.toLowerCase(),
|
||||
);
|
||||
return (
|
||||
<Button
|
||||
key={key}
|
||||
onClick={() => handleTagUpdate({ id: key, text: value.name })}
|
||||
size={'small'}
|
||||
style={isCN ? { flex: 1, height: 36 } : { flex: 1 }}
|
||||
type={isActive ? 'primary' : 'dashed'}
|
||||
>
|
||||
{isCN ? (
|
||||
<Flexbox gap={2}>
|
||||
<div style={{ fontSize: 12, fontWeight: 600, lineHeight: 1 }}>
|
||||
{value.langName}
|
||||
</div>
|
||||
<div style={{ fontSize: 12, lineHeight: 1, opacity: 0.75 }}>{value.name}</div>
|
||||
</Flexbox>
|
||||
) : (
|
||||
value.name
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</Flexbox>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default PromptPicker;
|
||||
@ -1,20 +1,28 @@
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import { useStyles } from '@/components/PromptEditor/style';
|
||||
import PromptPicker from '@/components/PromptEditor/PromptPicker';
|
||||
import { selectors, useAppStore } from '@/store';
|
||||
|
||||
import Prompt from './Prompt';
|
||||
|
||||
const PromptEditor = memo(() => {
|
||||
const { styles } = useStyles();
|
||||
const setting = useAppStore(selectors.currentSetting, isEqual);
|
||||
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>
|
||||
<Flexbox gap={16}>
|
||||
{setting.promptEditor && (
|
||||
<>
|
||||
<span style={{ marginBottom: -10 }}>{t('prompt.positive')}</span>
|
||||
<Prompt type="positive" />
|
||||
<span style={{ marginBottom: -10 }}>{t('prompt.negative')}</span>
|
||||
<Prompt type="negative" />
|
||||
</>
|
||||
)}
|
||||
<PromptPicker />
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@ -1,19 +1,9 @@
|
||||
import { createStyles } from 'antd-style';
|
||||
|
||||
export const useStyles = createStyles(({ css }) => ({
|
||||
buttonGroup: css`
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
`,
|
||||
promptView: css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
`,
|
||||
view: css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
margin-bottom: 1em;
|
||||
`,
|
||||
}));
|
||||
|
||||
@ -29,5 +29,5 @@ export const formatPrompt = (value: string) => {
|
||||
.replaceAll(',', ', ');
|
||||
return Converter.convertStr2Array(newItem).join(', ');
|
||||
});
|
||||
return textArray.map((tag) => genTagType({ id: tag, text: tag }));
|
||||
return textArray.map((tag) => genTagType({ id: tag.trim(), text: tag.trim() }));
|
||||
};
|
||||
|
||||
@ -1,16 +1,24 @@
|
||||
import { DraggablePanelBody } from '@lobehub/ui';
|
||||
import { Segmented } from 'antd';
|
||||
import { useTheme } from 'antd-style';
|
||||
import { consola } from 'consola';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { memo, useEffect, useRef } from 'react';
|
||||
import { memo, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import { PromptEditor } from '@/components';
|
||||
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);
|
||||
enum Tabs {
|
||||
Prompt = 'prompt',
|
||||
Setting = 'setting',
|
||||
}
|
||||
|
||||
const Inner = memo<DivProps>(() => {
|
||||
const theme = useTheme();
|
||||
const [tab, setTab] = useState<Tabs>(Tabs.Setting);
|
||||
const sidebarReference = useRef<HTMLDivElement>(null);
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
try {
|
||||
const sidebar = gradioApp().querySelector('#quicksettings');
|
||||
@ -23,8 +31,20 @@ const Inner = memo<DivProps>(() => {
|
||||
|
||||
return (
|
||||
<DraggablePanelBody>
|
||||
{setting.promptEditor && <PromptEditor />}
|
||||
<div ref={sidebarReference} />
|
||||
<Flexbox gap={16}>
|
||||
<Segmented
|
||||
block
|
||||
onChange={(value) => setTab(value as Tabs)}
|
||||
options={[
|
||||
{ label: t('sidebar.quickSetting'), value: Tabs.Setting },
|
||||
{ label: t('setting.promptEditor.title'), value: Tabs.Prompt },
|
||||
]}
|
||||
style={{ background: theme.colorBgContainer, width: '100%' }}
|
||||
value={tab}
|
||||
/>
|
||||
<div ref={sidebarReference} style={tab === Tabs.Setting ? {} : { display: 'none' }} />
|
||||
{tab === Tabs.Prompt && <PromptEditor />}
|
||||
</Flexbox>
|
||||
</DraggablePanelBody>
|
||||
);
|
||||
});
|
||||
|
||||
@ -2,12 +2,12 @@ import { Form } from '@lobehub/ui';
|
||||
import { Switch } from 'antd';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { Puzzle, TextCursorInput } from 'lucide-react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Footer from '@/features/Setting/Form/Footer';
|
||||
import { SettingItemGroup } from '@/features/Setting/Form/types';
|
||||
import { selectors, useAppStore } from '@/store';
|
||||
import { WebuiSetting, selectors, useAppStore } from '@/store';
|
||||
|
||||
const SettingForm = memo(() => {
|
||||
const setting = useAppStore(selectors.currentSetting, isEqual);
|
||||
@ -15,6 +15,11 @@ const SettingForm = memo(() => {
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onFinish = useCallback((value: WebuiSetting) => {
|
||||
onSetSetting(value);
|
||||
location.reload();
|
||||
}, []);
|
||||
|
||||
const experimental: SettingItemGroup = useMemo(
|
||||
() => ({
|
||||
children: [
|
||||
@ -61,7 +66,7 @@ const SettingForm = memo(() => {
|
||||
footer={<Footer />}
|
||||
initialValues={setting}
|
||||
items={[experimental, promptTextarea]}
|
||||
onFinish={onSetSetting}
|
||||
onFinish={onFinish}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -2,12 +2,12 @@ import { Form } from '@lobehub/ui';
|
||||
import { Segmented, Switch } from 'antd';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { Layout, TextCursorInput } from 'lucide-react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Footer from '@/features/Setting/Form/Footer';
|
||||
import { SettingItemGroup } from '@/features/Setting/Form/types';
|
||||
import { selectors, useAppStore } from '@/store';
|
||||
import { WebuiSetting, selectors, useAppStore } from '@/store';
|
||||
|
||||
const SettingForm = memo(() => {
|
||||
const setting = useAppStore(selectors.currentSetting, isEqual);
|
||||
@ -15,6 +15,11 @@ const SettingForm = memo(() => {
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onFinish = useCallback((value: WebuiSetting) => {
|
||||
onSetSetting(value);
|
||||
location.reload();
|
||||
}, []);
|
||||
|
||||
const layout: SettingItemGroup = useMemo(
|
||||
() => ({
|
||||
children: [
|
||||
@ -73,7 +78,7 @@ const SettingForm = memo(() => {
|
||||
footer={<Footer />}
|
||||
initialValues={setting}
|
||||
items={[layout, promptTextarea]}
|
||||
onFinish={onSetSetting}
|
||||
onFinish={onFinish}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -2,7 +2,7 @@ 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, useMemo, useState } from 'react';
|
||||
import { memo, useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Footer from '@/features/Setting/Form/Footer';
|
||||
@ -16,6 +16,11 @@ const SettingForm = memo(() => {
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onFinish = useCallback((value: WebuiSetting) => {
|
||||
onSetSetting(value);
|
||||
location.reload();
|
||||
}, []);
|
||||
|
||||
const quickSettingSidebar: SettingItemGroup = useMemo(
|
||||
() => ({
|
||||
children: [
|
||||
@ -132,7 +137,7 @@ const SettingForm = memo(() => {
|
||||
footer={<Footer />}
|
||||
initialValues={setting}
|
||||
items={[quickSettingSidebar, extraNetworkSidebar]}
|
||||
onFinish={onSetSetting}
|
||||
onFinish={onFinish}
|
||||
onValuesChange={(_, v) => setRawSetting(v)}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
|
||||
@ -32,6 +32,31 @@ export const getVersion = async(): Promise<string> => {
|
||||
return data.version;
|
||||
};
|
||||
|
||||
interface PromptData {
|
||||
[key: string]: {
|
||||
children: {
|
||||
[key: string]: {
|
||||
children: {
|
||||
[key: string]: {
|
||||
langName: string;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
langName: string;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
langName: string;
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const getPrompt = async(): Promise<PromptData> => {
|
||||
const res = await fetch('/lobe/prompt');
|
||||
const data = (await res.json()) as any;
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getLocaleOptions = async(): Promise<SelectProps['options']> => {
|
||||
const res = await fetch('/lobe/locales/options');
|
||||
const data = (await res.json()) as SelectProps['options'];
|
||||
|
||||
@ -2,9 +2,12 @@ import { DEFAULT_SETTING } from './initialState';
|
||||
import type { Store } from './store';
|
||||
|
||||
const currentSetting = (s: Store) => ({ ...DEFAULT_SETTING, ...s.setting });
|
||||
|
||||
const currentLanguage = (s: Store) => currentSetting(s).i18n;
|
||||
const currentTab = (s: Store) => s.currentTab;
|
||||
const themeMode = (s: Store) => s.themeMode;
|
||||
export const selectors = {
|
||||
currentLanguage,
|
||||
currentSetting,
|
||||
currentTab,
|
||||
themeMode,
|
||||
|
||||
@ -2,8 +2,17 @@ import { Theme, css } from 'antd-style';
|
||||
import { readableColor } from 'polished';
|
||||
|
||||
export default (token: Theme) => css`
|
||||
body {
|
||||
html,
|
||||
body,
|
||||
#__next,
|
||||
.ant-app {
|
||||
position: relative;
|
||||
overscroll-behavior: none;
|
||||
height: 100% !important;
|
||||
min-height: 100% !important;
|
||||
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user