Expose image studio flow and related admin controls

Bundle the current backend and web changes into one publishable snapshot
from the server worktree so the remote mirror reflects the deployed source.

Constraint: Push the existing mixed worktree from /docker/new-api/src without reshaping files
Rejected: Split into feature-specific commits | current worktree is already a single undifferentiated snapshot
Confidence: medium
Scope-risk: moderate
Directive: Split future feature work before deployment to preserve clearer history
Tested: git status inspection; git diff --stat review
Not-tested: build, lint, unit tests, integration tests
This commit is contained in:
OpenClaw Task Bot
2026-05-05 12:16:54 +08:00
parent 58c0258362
commit 92ee465a41
23 changed files with 2694 additions and 202 deletions

11
web/src/App.jsx vendored
View File

@@ -47,6 +47,7 @@ import Playground from './pages/Playground';
import Subscription from './pages/Subscription';
import OAuth2Callback from './components/auth/OAuth2Callback';
import PersonalSetting from './components/settings/PersonalSetting';
import ImageStudio from './pages/ImageStudio';
import Setup from './pages/Setup';
import SetupCheck from './components/layout/SetupCheck';
@@ -277,6 +278,16 @@ function App() {
</PrivateRoute>
}
/>
<Route
path='/console/image-studio'
element={
<PrivateRoute>
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
<ImageStudio />
</Suspense>
</PrivateRoute>
}
/>
<Route
path='/console/log'
element={

View File

@@ -49,6 +49,7 @@ const routerMap = {
deployment: '/console/deployment',
playground: '/console/playground',
personal: '/console/personal',
image_studio: '/console/image-studio',
};
const SiderBar = ({ onNavigate = () => {} }) => {
@@ -145,6 +146,18 @@ const SiderBar = ({ onNavigate = () => {} }) => {
return filteredItems;
}, [t, isModuleVisible]);
const otherItems = useMemo(() => {
const items = [
{
text: t('图片制作'),
itemKey: 'image_studio',
to: '/image-studio',
},
];
return items.filter((item) => isModuleVisible('other', item.itemKey));
}, [t, isModuleVisible]);
const adminItems = useMemo(() => {
const items = [
{
@@ -475,6 +488,19 @@ const SiderBar = ({ onNavigate = () => {} }) => {
</>
)}
{/* 其他功能区域 */}
{hasSectionVisibleModules('other') && (
<>
<Divider className='sidebar-divider' />
<div>
{!collapsed && (
<div className='sidebar-group-label'>{t('其他功能')}</div>
)}
{otherItems.map((item) => renderNavItem(item))}
</div>
</>
)}
{/* 管理员区域 - 只在管理员时显示且配置允许时显示 */}
{isAdmin() && hasSectionVisibleModules('admin') && (
<>

View File

@@ -63,36 +63,9 @@ const NotificationSettings = ({
// 左侧边栏设置相关状态
const [sidebarLoading, setSidebarLoading] = useState(false);
const [activeTabKey, setActiveTabKey] = useState('notification');
const [sidebarModulesUser, setSidebarModulesUser] = useState({
chat: {
enabled: true,
playground: true,
chat: true,
},
console: {
enabled: true,
detail: true,
token: true,
log: true,
midjourney: true,
task: true,
},
personal: {
enabled: true,
topup: true,
personal: true,
},
admin: {
enabled: true,
channel: true,
models: true,
deployment: true,
subscription: true,
redemption: true,
user: true,
setting: true,
},
});
const [sidebarModulesUser, setSidebarModulesUser] = useState(
mergeAdminConfig(null),
);
const [adminConfig, setAdminConfig] = useState(null);
// 使用后端权限验证替代前端角色判断
@@ -155,29 +128,7 @@ const NotificationSettings = ({
};
const resetSidebarModules = () => {
const defaultConfig = {
chat: { enabled: true, playground: true, chat: true },
console: {
enabled: true,
detail: true,
token: true,
log: true,
midjourney: true,
task: true,
},
personal: { enabled: true, topup: true, personal: true },
admin: {
enabled: true,
channel: true,
models: true,
deployment: true,
subscription: true,
redemption: true,
user: true,
setting: true,
},
};
setSidebarModulesUser(defaultConfig);
setSidebarModulesUser(mergeAdminConfig(null));
};
// 加载左侧边栏配置
@@ -207,7 +158,7 @@ const NotificationSettings = ({
} else {
userConf = userRes.data.data.sidebar_modules;
}
setSidebarModulesUser(userConf);
setSidebarModulesUser(mergeAdminConfig(userConf));
}
} catch (error) {
console.error('加载边栏配置失败:', error);
@@ -287,6 +238,18 @@ const NotificationSettings = ({
],
},
// 管理员区域:根据后端权限控制显示
{
key: 'other',
title: t('其他功能区域'),
description: t('图片与扩展功能'),
modules: [
{
key: 'image_studio',
title: t('图片制作'),
description: t('生成图片和编辑图片'),
},
],
},
{
key: 'admin',
title: t('管理员区域'),

View File

@@ -75,6 +75,7 @@ import {
Package,
Server,
CalendarClock,
Sparkles,
} from 'lucide-react';
import {
SiAtlassian,
@@ -146,6 +147,8 @@ export function getLucideIcon(key, selected = false) {
return <CalendarClock {...commonProps} color={iconColor} />;
case 'setting':
return <Settings {...commonProps} color={iconColor} />;
case 'image_studio':
return <Sparkles {...commonProps} color={iconColor} />;
default:
return <CircleUser {...commonProps} color={iconColor} />;
}

View File

@@ -121,13 +121,12 @@ if (isMobileScreen) {
export function showError(error) {
console.error(error);
if (error.message) {
if (error?.message) {
if (error.name === 'AxiosError') {
switch (error.response.status) {
const status = error.response?.status;
switch (status) {
case 401:
// 清除用户状态
localStorage.removeItem('user');
// toast.error('错误:未登录或登录已过期,请重新登录!', showErrorOptions);
window.location.href = '/login?expired=true';
break;
case 429:

View File

@@ -44,6 +44,10 @@ export const DEFAULT_ADMIN_CONFIG = {
topup: true,
personal: true,
},
other: {
enabled: true,
image_studio: true,
},
admin: {
enabled: true,
channel: true,

File diff suppressed because it is too large Load Diff

View File

@@ -35,6 +35,7 @@ export default function SettingsSensitiveWords(props) {
CheckSensitiveEnabled: false,
CheckSensitiveOnPromptEnabled: false,
SensitiveWords: '',
ImageStudioSafetyPrompt: '',
});
const refForm = useRef();
const [inputsRow, setInputsRow] = useState(inputs);
@@ -130,8 +131,8 @@ export default function SettingsSensitiveWords(props) {
<Col xs={24} sm={12} md={8} lg={8} xl={8}>
<Form.TextArea
label={t('屏蔽词列表')}
extraText={t('一行一个屏蔽词,不需要符号分割')}
placeholder={t('一行一个屏蔽词,不需要符号分割')}
extraText={t('一行一个屏蔽词,不需要符号分割。该词库也会用于图片生图提示词审核')}
placeholder={t('一行一个屏蔽词,不需要符号分割。该词库也会用于图片生图提示词审核')}
field={'SensitiveWords'}
onChange={(value) =>
setInputs({
@@ -144,6 +145,24 @@ export default function SettingsSensitiveWords(props) {
/>
</Col>
</Row>
<Row>
<Col xs={24} sm={12} md={8} lg={8} xl={8}>
<Form.TextArea
label={t('图片生图安全提示词')}
extraText={t('仅用于图片生成/编辑,请写入暴力、色情、赌博、毒品、中国政治敏感、恐怖、恶心等禁止内容约束')}
placeholder={t('仅用于图片生成/编辑,请写入暴力、色情、赌博、毒品、中国政治敏感、恐怖、恶心等禁止内容约束')}
field={'ImageStudioSafetyPrompt'}
onChange={(value) =>
setInputs({
...inputs,
ImageStudioSafetyPrompt: value,
})
}
style={{ fontFamily: 'JetBrains Mono, Consolas' }}
autosize={{ minRows: 10, maxRows: 20 }}
/>
</Col>
</Row>
<Row>
<Button size='default' onClick={onSubmit}>
{t('保存屏蔽词过滤设置')}

View File

@@ -30,6 +30,7 @@ import {
} from '@douyinfe/semi-ui';
import { API, showSuccess, showError } from '../../../helpers';
import { StatusContext } from '../../../context/Status';
import { mergeAdminConfig } from '../../../hooks/common/useSidebar';
const { Text } = Typography;
@@ -39,36 +40,9 @@ export default function SettingsSidebarModulesAdmin(props) {
const [statusState, statusDispatch] = useContext(StatusContext);
// 左侧边栏模块管理状态(管理员全局控制)
const [sidebarModulesAdmin, setSidebarModulesAdmin] = useState({
chat: {
enabled: true,
playground: true,
chat: true,
},
console: {
enabled: true,
detail: true,
token: true,
log: true,
midjourney: true,
task: true,
},
personal: {
enabled: true,
topup: true,
personal: true,
},
admin: {
enabled: true,
channel: true,
models: true,
deployment: true,
redemption: true,
user: true,
subscription: true,
setting: true,
},
});
const [sidebarModulesAdmin, setSidebarModulesAdmin] = useState(
mergeAdminConfig(null),
);
// 处理区域级别开关变更
function handleSectionChange(sectionKey) {
@@ -100,36 +74,7 @@ export default function SettingsSidebarModulesAdmin(props) {
// 重置为默认配置
function resetSidebarModules() {
const defaultModules = {
chat: {
enabled: true,
playground: true,
chat: true,
},
console: {
enabled: true,
detail: true,
token: true,
log: true,
midjourney: true,
task: true,
},
personal: {
enabled: true,
topup: true,
personal: true,
},
admin: {
enabled: true,
channel: true,
models: true,
deployment: true,
redemption: true,
user: true,
subscription: true,
setting: true,
},
};
const defaultModules = mergeAdminConfig(null);
setSidebarModulesAdmin(defaultModules);
showSuccess(t('已重置为默认配置'));
}
@@ -174,32 +119,9 @@ export default function SettingsSidebarModulesAdmin(props) {
if (props.options && props.options.SidebarModulesAdmin) {
try {
const modules = JSON.parse(props.options.SidebarModulesAdmin);
setSidebarModulesAdmin(modules);
setSidebarModulesAdmin(mergeAdminConfig(modules));
} catch (error) {
// 使用默认配置
const defaultModules = {
chat: { enabled: true, playground: true, chat: true },
console: {
enabled: true,
detail: true,
token: true,
log: true,
midjourney: true,
task: true,
},
personal: { enabled: true, topup: true, personal: true },
admin: {
enabled: true,
channel: true,
models: true,
deployment: true,
redemption: true,
user: true,
subscription: true,
setting: true,
},
};
setSidebarModulesAdmin(defaultModules);
setSidebarModulesAdmin(mergeAdminConfig(null));
}
}
}, [props.options]);
@@ -248,6 +170,18 @@ export default function SettingsSidebarModulesAdmin(props) {
},
],
},
{
key: 'other',
title: t('其他功能区域'),
description: t('图片与扩展功能'),
modules: [
{
key: 'image_studio',
title: t('图片制作'),
description: t('生成图片和编辑图片'),
},
],
},
{
key: 'admin',
title: t('管理员区域'),