Compare commits
2 Commits
9137ae3063
...
a1ac879c6c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1ac879c6c | ||
|
|
73274300ec |
@@ -195,12 +195,12 @@ const Header = () => {
|
|||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText>个人资料</ListItemText>
|
<ListItemText>个人资料</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{/* 系统设置 */}
|
{/* 模型配置 */}
|
||||||
<MenuItem onClick={() => navigate('/setting/system')} sx={{ py: 1 }}>
|
<MenuItem onClick={() => navigate('/setting/models')} sx={{ py: 1 }}>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<SettingsIcon fontSize="small" />
|
<SettingsIcon fontSize="small" />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText>系统设置</ListItemText>
|
<ListItemText>模型配置</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useUserData } from "./useUserData";
|
import { useUserData } from "./useUserData";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState, useCallback } from "react";
|
||||||
import logger from "@/utils/logger";
|
import logger from "@/utils/logger";
|
||||||
import type { IUserInfo } from "@/interfaces/database/user-setting";
|
import type { IUserInfo, ISystemStatus } from "@/interfaces/database/user-setting";
|
||||||
import userService from "@/services/user_service";
|
import userService from "@/services/user_service";
|
||||||
import { rsaPsw } from "../utils/encryption";
|
import { rsaPsw } from "../utils/encryption";
|
||||||
import type { IFactory, IMyLlmModel } from "@/interfaces/database/llm";
|
import type { IFactory, IMyLlmModel } from "@/interfaces/database/llm";
|
||||||
@@ -92,3 +92,160 @@ export function useLlmModelSetting() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统状态设置
|
||||||
|
*/
|
||||||
|
export function useSystemStatus() {
|
||||||
|
const [systemStatus, setSystemStatus] = useState<ISystemStatus | null>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const fetchSystemStatus = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
const res = await userService.system_status();
|
||||||
|
if (res.data.code === 0) {
|
||||||
|
setSystemStatus(res.data.data);
|
||||||
|
} else {
|
||||||
|
throw new Error(res.data.message || '获取系统状态失败');
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
const errorMessage = error.response?.data?.message || error.message || '获取系统状态失败';
|
||||||
|
setError(errorMessage);
|
||||||
|
logger.error('获取系统状态失败:', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
systemStatus,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
fetchSystemStatus,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 团队设置
|
||||||
|
*/
|
||||||
|
export function useTeamSetting() {
|
||||||
|
const { userInfo, tenantInfo, tenantList, refreshUserData } = useUserData();
|
||||||
|
const [tenantUsers, setTenantUsers] = useState<any[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// 获取租户用户列表
|
||||||
|
const fetchTenantUsers = useCallback(async () => {
|
||||||
|
if (!tenantInfo?.tenant_id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await userService.listTenantUser(tenantInfo.tenant_id);
|
||||||
|
if (response.data.code === 0) {
|
||||||
|
setTenantUsers(response.data.data || []);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('获取租户用户失败:', error);
|
||||||
|
}
|
||||||
|
}, [tenantInfo?.tenant_id]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (tenantInfo?.tenant_id) {
|
||||||
|
fetchTenantUsers();
|
||||||
|
}
|
||||||
|
}, [fetchTenantUsers]);
|
||||||
|
|
||||||
|
const inviteUser = async (email: string) => {
|
||||||
|
if (!email.trim() || !tenantInfo?.tenant_id) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await userService.addTenantUser(tenantInfo.tenant_id, email.trim());
|
||||||
|
if (response.data.code === 0) {
|
||||||
|
await fetchTenantUsers();
|
||||||
|
await refreshUserData();
|
||||||
|
return { success: true };
|
||||||
|
} else {
|
||||||
|
const errorMsg = response.data.message || '邀请失败';
|
||||||
|
setError(errorMsg);
|
||||||
|
return { success: false, error: errorMsg };
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
const errorMsg = error.message || '邀请失败';
|
||||||
|
setError(errorMsg);
|
||||||
|
return { success: false, error: errorMsg };
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteUser = async (userId: string) => {
|
||||||
|
if (!tenantInfo?.tenant_id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await userService.deleteTenantUser({
|
||||||
|
tenantId: tenantInfo.tenant_id,
|
||||||
|
userId
|
||||||
|
});
|
||||||
|
if (response.data.code === 0) {
|
||||||
|
await fetchTenantUsers();
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
return { success: false };
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('删除用户失败:', error);
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const agreeTenant = async (tenantId: string) => {
|
||||||
|
try {
|
||||||
|
const response = await userService.agreeTenant(tenantId);
|
||||||
|
if (response.data.code === 0) {
|
||||||
|
await refreshUserData();
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
return { success: false };
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('同意加入失败:', error);
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const quitTenant = async (tenantId: string) => {
|
||||||
|
if (!userInfo?.id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await userService.deleteTenantUser({
|
||||||
|
tenantId,
|
||||||
|
userId: userInfo.id
|
||||||
|
});
|
||||||
|
if (response.data.code === 0) {
|
||||||
|
await refreshUserData();
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
return { success: false };
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('退出租户失败:', error);
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
userInfo,
|
||||||
|
tenantInfo,
|
||||||
|
tenantList,
|
||||||
|
tenantUsers,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
inviteUser,
|
||||||
|
deleteUser,
|
||||||
|
agreeTenant,
|
||||||
|
quitTenant,
|
||||||
|
refreshUserData,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ export interface ISystemStatus {
|
|||||||
database: Database;
|
database: Database;
|
||||||
/** Redis状态 */
|
/** Redis状态 */
|
||||||
redis: Redis;
|
redis: Redis;
|
||||||
|
/** docker引擎状态 */
|
||||||
|
doc_engine: DockerEngine;
|
||||||
/** 任务执行器心跳信息 */
|
/** 任务执行器心跳信息 */
|
||||||
task_executor_heartbeat: Record<string, TaskExecutorHeartbeatItem[]>;
|
task_executor_heartbeat: Record<string, TaskExecutorHeartbeatItem[]>;
|
||||||
}
|
}
|
||||||
@@ -117,6 +119,8 @@ export interface Storage {
|
|||||||
elapsed: number;
|
elapsed: number;
|
||||||
/** 错误信息 */
|
/** 错误信息 */
|
||||||
error: string;
|
error: string;
|
||||||
|
/** storage */
|
||||||
|
storage: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -130,6 +134,8 @@ export interface Database {
|
|||||||
elapsed: number;
|
elapsed: number;
|
||||||
/** 错误信息 */
|
/** 错误信息 */
|
||||||
error: string;
|
error: string;
|
||||||
|
/** 数据库名称 */
|
||||||
|
database: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -149,6 +155,43 @@ interface Es {
|
|||||||
active_shards: number;
|
active_shards: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DockerEngine {
|
||||||
|
/** 活跃主分片数 */
|
||||||
|
active_primary_shards: number;
|
||||||
|
/** 活跃分片数 */
|
||||||
|
active_shards: number;
|
||||||
|
/** 活跃分片数占比 */
|
||||||
|
active_shards_percent_as_number: number;
|
||||||
|
/** 集群名称 */
|
||||||
|
cluster_name: string;
|
||||||
|
/** 延迟未分配分片数 */
|
||||||
|
delayed_unassigned_shards: number;
|
||||||
|
/** 初始化分片数 */
|
||||||
|
initializing_shards: number;
|
||||||
|
/** 数据节点数量 */
|
||||||
|
number_of_data_nodes: number;
|
||||||
|
/** 正在处理的获取请求数 */
|
||||||
|
number_of_in_flight_fetch: number;
|
||||||
|
/** 节点数量 */
|
||||||
|
number_of_nodes: number;
|
||||||
|
/** 待处理任务数 */
|
||||||
|
number_of_pending_tasks: number;
|
||||||
|
/** 迁移分片数 */
|
||||||
|
relocating_shards: number;
|
||||||
|
/** 服务状态 */
|
||||||
|
status: string;
|
||||||
|
/** 任务最大等待时间(毫秒) */
|
||||||
|
task_max_waiting_in_queue_millis: number;
|
||||||
|
/** 是否超时 */
|
||||||
|
timed_out: boolean;
|
||||||
|
/** 类型 */
|
||||||
|
type: string;
|
||||||
|
/** 未分配分片数 */
|
||||||
|
unassigned_shards: number;
|
||||||
|
/** 响应耗时 */
|
||||||
|
elapsed: number;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 租户用户接口
|
* 租户用户接口
|
||||||
* 定义租户下用户的信息
|
* 定义租户下用户的信息
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export const useDialogState = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// API Key 对话框管理
|
// API Key 对话框管理
|
||||||
export const useApiKeyDialog = () => {
|
export const useApiKeyDialog = (onSuccess?: () => void) => {
|
||||||
const dialogState = useDialogState();
|
const dialogState = useDialogState();
|
||||||
const showMessage = useMessage();
|
const showMessage = useMessage();
|
||||||
const [factoryName, setFactoryName] = useState('');
|
const [factoryName, setFactoryName] = useState('');
|
||||||
@@ -77,12 +77,17 @@ export const useApiKeyDialog = () => {
|
|||||||
await userService.set_api_key(params);
|
await userService.set_api_key(params);
|
||||||
showMessage.success('API Key 配置成功');
|
showMessage.success('API Key 配置成功');
|
||||||
dialogState.closeDialog();
|
dialogState.closeDialog();
|
||||||
|
|
||||||
|
// 调用成功回调
|
||||||
|
if (onSuccess) {
|
||||||
|
onSuccess();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('API Key 配置失败:', error);
|
logger.error('API Key 配置失败:', error);
|
||||||
} finally {
|
} finally {
|
||||||
dialogState.setLoading(false);
|
dialogState.setLoading(false);
|
||||||
}
|
}
|
||||||
}, [factoryName, dialogState]);
|
}, [factoryName, dialogState, onSuccess]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...dialogState,
|
...dialogState,
|
||||||
@@ -191,7 +196,7 @@ export const useOllamaDialog = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 删除操作管理
|
// 删除操作管理
|
||||||
export const useDeleteOperations = () => {
|
export const useDeleteOperations = (onSuccess?: () => void) => {
|
||||||
const showMessage = useMessage();
|
const showMessage = useMessage();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
@@ -203,12 +208,17 @@ export const useDeleteOperations = () => {
|
|||||||
llm_name: modelName,
|
llm_name: modelName,
|
||||||
});
|
});
|
||||||
showMessage.success('模型删除成功');
|
showMessage.success('模型删除成功');
|
||||||
|
|
||||||
|
// 调用成功回调
|
||||||
|
if (onSuccess) {
|
||||||
|
onSuccess();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('模型删除失败:', error);
|
logger.error('模型删除失败:', error);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [onSuccess]);
|
||||||
|
|
||||||
const deleteFactory = useCallback(async (factoryName: string) => {
|
const deleteFactory = useCallback(async (factoryName: string) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -217,12 +227,17 @@ export const useDeleteOperations = () => {
|
|||||||
llm_factory: factoryName,
|
llm_factory: factoryName,
|
||||||
});
|
});
|
||||||
showMessage.success('模型工厂删除成功');
|
showMessage.success('模型工厂删除成功');
|
||||||
|
|
||||||
|
// 调用成功回调
|
||||||
|
if (onSuccess) {
|
||||||
|
onSuccess();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('模型工厂删除失败:', error);
|
logger.error('模型工厂删除失败:', error);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [onSuccess]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loading,
|
loading,
|
||||||
@@ -232,7 +247,7 @@ export const useDeleteOperations = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 系统默认模型设置
|
// 系统默认模型设置
|
||||||
export const useSystemModelSetting = () => {
|
export const useSystemModelSetting = (onSuccess?: () => void) => {
|
||||||
const dialogState = useDialogState();
|
const dialogState = useDialogState();
|
||||||
const showMessage = useMessage();
|
const showMessage = useMessage();
|
||||||
|
|
||||||
@@ -301,6 +316,11 @@ export const useSystemModelSetting = () => {
|
|||||||
showMessage.success('系统默认模型设置成功');
|
showMessage.success('系统默认模型设置成功');
|
||||||
dialogState.closeDialog();
|
dialogState.closeDialog();
|
||||||
fetchTenantInfo();
|
fetchTenantInfo();
|
||||||
|
|
||||||
|
// 调用成功回调
|
||||||
|
if (onSuccess) {
|
||||||
|
onSuccess();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('系统默认模型设置失败:', error);
|
logger.error('系统默认模型设置失败:', error);
|
||||||
showMessage.error('系统默认模型设置失败');
|
showMessage.error('系统默认模型设置失败');
|
||||||
@@ -308,7 +328,7 @@ export const useSystemModelSetting = () => {
|
|||||||
} finally {
|
} finally {
|
||||||
dialogState.setLoading(false);
|
dialogState.setLoading(false);
|
||||||
}
|
}
|
||||||
}, [dialogState]);
|
}, [dialogState, onSuccess]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...dialogState,
|
...dialogState,
|
||||||
@@ -319,13 +339,13 @@ export const useSystemModelSetting = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 统一的模型对话框管理器
|
// 统一的模型对话框管理器
|
||||||
export const useModelDialogs = () => {
|
export const useModelDialogs = (onSuccess?: () => void) => {
|
||||||
const apiKeyDialog = useApiKeyDialog();
|
const apiKeyDialog = useApiKeyDialog(onSuccess);
|
||||||
const azureDialog = useAzureOpenAIDialog();
|
const azureDialog = useAzureOpenAIDialog();
|
||||||
const bedrockDialog = useBedrockDialog();
|
const bedrockDialog = useBedrockDialog();
|
||||||
const ollamaDialog = useOllamaDialog();
|
const ollamaDialog = useOllamaDialog();
|
||||||
const systemDialog = useSystemModelSetting();
|
const systemDialog = useSystemModelSetting(onSuccess);
|
||||||
const deleteOps = useDeleteOperations();
|
const deleteOps = useDeleteOperations(onSuccess);
|
||||||
|
|
||||||
// 根据工厂类型打开对应的对话框
|
// 根据工厂类型打开对应的对话框
|
||||||
const openFactoryDialog = useCallback((factoryName: string, data?: any, isEdit = false) => {
|
const openFactoryDialog = useCallback((factoryName: string, data?: any, isEdit = false) => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useCallback } from 'react';
|
import React, { useState, useCallback, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
@@ -29,8 +29,6 @@ import type { IFactory, IMyLlmModel, ILlmItem } from '@/interfaces/database/llm'
|
|||||||
import LLMFactoryCard, { MODEL_TYPE_COLORS } from './components/LLMFactoryCard';
|
import LLMFactoryCard, { MODEL_TYPE_COLORS } from './components/LLMFactoryCard';
|
||||||
import { ModelDialogs } from './components/ModelDialogs';
|
import { ModelDialogs } from './components/ModelDialogs';
|
||||||
import { useDialog } from '@/hooks/useDialog';
|
import { useDialog } from '@/hooks/useDialog';
|
||||||
import logger from '@/utils/logger';
|
|
||||||
import { useMessage } from '@/hooks/useSnackbar';
|
|
||||||
|
|
||||||
function MyLlmGridItem({ model, onDelete }: { model: ILlmItem, onDelete: (model: ILlmItem) => void }) {
|
function MyLlmGridItem({ model, onDelete }: { model: ILlmItem, onDelete: (model: ILlmItem) => void }) {
|
||||||
return (
|
return (
|
||||||
@@ -67,12 +65,29 @@ function MyLlmGridItem({ model, onDelete }: { model: ILlmItem, onDelete: (model:
|
|||||||
// 主页面组件
|
// 主页面组件
|
||||||
function ModelsPage() {
|
function ModelsPage() {
|
||||||
const { llmFactory, myLlm, refreshLlmModel } = useLlmModelSetting();
|
const { llmFactory, myLlm, refreshLlmModel } = useLlmModelSetting();
|
||||||
const modelDialogs = useModelDialogs();
|
const modelDialogs = useModelDialogs(refreshLlmModel);
|
||||||
|
|
||||||
|
// 折叠状态管理 - 使用 Map 来管理每个工厂的折叠状态
|
||||||
|
const [collapsedFactories, setCollapsedFactories] = useState<Record<string, boolean>>({});
|
||||||
|
|
||||||
|
// 切换工厂折叠状态
|
||||||
|
const toggleFactoryCollapse = useCallback((factoryName: string) => {
|
||||||
|
setCollapsedFactories(prev => ({
|
||||||
|
...prev,
|
||||||
|
[factoryName]: !prev[factoryName]
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const showLlmFactory = useMemo(() => {
|
||||||
|
const modelFactoryNames = Object.keys(myLlm || {});
|
||||||
|
const filterFactory = llmFactory?.filter(factory => !modelFactoryNames.includes(factory.name));
|
||||||
|
return filterFactory || [];
|
||||||
|
}, [llmFactory, myLlm]);
|
||||||
|
|
||||||
// 处理配置模型工厂
|
// 处理配置模型工厂
|
||||||
const handleConfigureFactory = useCallback((factory: IFactory) => {
|
const handleConfigureFactory = useCallback((factory: IFactory) => {
|
||||||
modelDialogs.apiKeyDialog.openApiKeyDialog(factory.name);
|
modelDialogs.apiKeyDialog.openApiKeyDialog(factory.name);
|
||||||
}, [modelDialogs, refreshLlmModel]);
|
}, [modelDialogs]);
|
||||||
|
|
||||||
const dialog = useDialog();
|
const dialog = useDialog();
|
||||||
|
|
||||||
@@ -84,10 +99,9 @@ function ModelsPage() {
|
|||||||
showCancel: true,
|
showCancel: true,
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
await modelDialogs.deleteOps.deleteLlm(factoryName, modelName);
|
await modelDialogs.deleteOps.deleteLlm(factoryName, modelName);
|
||||||
await refreshLlmModel();
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}, [dialog, refreshLlmModel]);
|
}, [dialog, modelDialogs.deleteOps]);
|
||||||
|
|
||||||
// 处理删除模型工厂
|
// 处理删除模型工厂
|
||||||
const handleDeleteFactory = useCallback(async (factoryName: string) => {
|
const handleDeleteFactory = useCallback(async (factoryName: string) => {
|
||||||
@@ -97,10 +111,9 @@ function ModelsPage() {
|
|||||||
showCancel: true,
|
showCancel: true,
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
await modelDialogs.deleteOps.deleteFactory(factoryName);
|
await modelDialogs.deleteOps.deleteFactory(factoryName);
|
||||||
await refreshLlmModel();
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}, [dialog, refreshLlmModel]);
|
}, [dialog, modelDialogs.deleteOps]);
|
||||||
|
|
||||||
if (!llmFactory || !myLlm) {
|
if (!llmFactory || !myLlm) {
|
||||||
return (
|
return (
|
||||||
@@ -150,7 +163,23 @@ function ModelsPage() {
|
|||||||
<Grid size={12} key={factoryName}>
|
<Grid size={12} key={factoryName}>
|
||||||
<Card variant="outlined">
|
<Card variant="outlined">
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
cursor: 'pointer',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.04)'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onClick={() => toggleFactoryCollapse(factoryName)}
|
||||||
|
>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
{/* 折叠/展开图标 */}
|
||||||
|
<IconButton size="small">
|
||||||
|
{collapsedFactories[factoryName] ? <ExpandMoreIcon /> : <ExpandLessIcon />}
|
||||||
|
</IconButton>
|
||||||
<Box>
|
<Box>
|
||||||
{/* 模型工厂名称 */}
|
{/* 模型工厂名称 */}
|
||||||
<Typography variant="h6" gutterBottom>
|
<Typography variant="h6" gutterBottom>
|
||||||
@@ -171,8 +200,9 @@ function ModelsPage() {
|
|||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
</Box>
|
||||||
{/* edit and delete factory button */}
|
{/* edit and delete factory button */}
|
||||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
<Box sx={{ display: 'flex', gap: 1 }} onClick={(e) => e.stopPropagation()}>
|
||||||
<Button
|
<Button
|
||||||
variant='contained' color='primary' startIcon={<EditIcon />}
|
variant='contained' color='primary' startIcon={<EditIcon />}
|
||||||
onClick={() => modelDialogs.apiKeyDialog.openApiKeyDialog(factoryName)}
|
onClick={() => modelDialogs.apiKeyDialog.openApiKeyDialog(factoryName)}
|
||||||
@@ -187,7 +217,9 @@ function ModelsPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
{/* 模型列表 */}
|
{/* 模型列表 - 使用 Collapse 组件包装 */}
|
||||||
|
<Collapse in={!collapsedFactories[factoryName]} timeout="auto" unmountOnExit>
|
||||||
|
<Box sx={{ mt: 2 }}>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
{group.llm.map((model) => (
|
{group.llm.map((model) => (
|
||||||
<MyLlmGridItem
|
<MyLlmGridItem
|
||||||
@@ -197,6 +229,8 @@ function ModelsPage() {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
</Collapse>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -218,7 +252,7 @@ function ModelsPage() {
|
|||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
{
|
{
|
||||||
llmFactory.map((factory) => (
|
showLlmFactory.map((factory) => (
|
||||||
<Grid size={{ xs: 6, sm: 4, md: 3 }} key={factory.name}>
|
<Grid size={{ xs: 6, sm: 4, md: 3 }} key={factory.name}>
|
||||||
<LLMFactoryCard
|
<LLMFactoryCard
|
||||||
key={factory.name}
|
key={factory.name}
|
||||||
|
|||||||
@@ -1,8 +1,195 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
Grid,
|
||||||
|
Chip,
|
||||||
|
CircularProgress,
|
||||||
|
Alert,
|
||||||
|
Divider,
|
||||||
|
Paper,
|
||||||
|
} from '@mui/material';
|
||||||
|
import {
|
||||||
|
Storage as StorageIcon,
|
||||||
|
Memory as RedisIcon,
|
||||||
|
Search as SearchIcon,
|
||||||
|
Computer as DocEngineIcon,
|
||||||
|
Favorite as HeartbeatIcon,
|
||||||
|
Settings as DefaultIcon,
|
||||||
|
} from '@mui/icons-material';
|
||||||
|
import { useSystemStatus } from '@/hooks/setting-hooks';
|
||||||
|
import type { ISystemStatus } from '@/interfaces/database/user-setting';
|
||||||
|
|
||||||
|
// 状态颜色映射
|
||||||
|
const STATUS_COLORS = {
|
||||||
|
green: 'success' as const,
|
||||||
|
red: 'error' as const,
|
||||||
|
yellow: 'warning' as const,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 组件标题映射
|
||||||
|
const TITLE_MAP = {
|
||||||
|
doc_engine: 'Doc Engine',
|
||||||
|
storage: 'Object Storage',
|
||||||
|
redis: 'Redis',
|
||||||
|
database: 'Database',
|
||||||
|
es: 'Elasticsearch',
|
||||||
|
task_executor_heartbeat: 'Task Executor',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 图标映射
|
||||||
|
const ICON_MAP = {
|
||||||
|
es: SearchIcon,
|
||||||
|
doc_engine: DocEngineIcon,
|
||||||
|
redis: RedisIcon,
|
||||||
|
storage: StorageIcon,
|
||||||
|
task_executor_heartbeat: HeartbeatIcon,
|
||||||
|
};
|
||||||
|
|
||||||
function SystemSetting() {
|
function SystemSetting() {
|
||||||
|
const { systemStatus, loading, error, fetchSystemStatus } = useSystemStatus();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchSystemStatus();
|
||||||
|
}, [fetchSystemStatus]);
|
||||||
|
|
||||||
|
const renderSystemInfo = (key: string, info: any) => {
|
||||||
|
// 跳过task_executor_heartbeat,因为它需要特殊的图表组件
|
||||||
|
if (key.startsWith('task_executor_heartbeat')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const IconComponent = ICON_MAP[key as keyof typeof ICON_MAP] || DefaultIcon;
|
||||||
|
const title = TITLE_MAP[key as keyof typeof TITLE_MAP] || key;
|
||||||
|
const status = info?.status || 'unknown';
|
||||||
|
const chipColor = STATUS_COLORS[status as keyof typeof STATUS_COLORS] || 'default';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Grid size={12} key={key}>
|
||||||
<h1>System Setting</h1>
|
<Card
|
||||||
</div>
|
sx={{
|
||||||
|
height: '100%',
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
'&:hover': {
|
||||||
|
transform: 'translateY(-4px)',
|
||||||
|
boxShadow: 4,
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardContent>
|
||||||
|
<Box display="flex" alignItems="center" gap={2} mb={2}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
p: 1,
|
||||||
|
borderRadius: 2,
|
||||||
|
backgroundColor: 'primary.main',
|
||||||
|
color: 'white',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconComponent />
|
||||||
|
</Box>
|
||||||
|
<Typography variant="h6" component="h3" sx={{ flexGrow: 1 }}>
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
<Chip
|
||||||
|
label={status.toUpperCase()}
|
||||||
|
color={chipColor}
|
||||||
|
size="small"
|
||||||
|
variant="filled"
|
||||||
|
sx={{ fontWeight: 'bold' }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Divider sx={{ mb: 2 }} />
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
{Object.keys(info)
|
||||||
|
.filter((x) => x !== 'status')
|
||||||
|
.map((x) => (
|
||||||
|
<Box
|
||||||
|
key={x}
|
||||||
|
display="flex"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
mb={1}
|
||||||
|
sx={{
|
||||||
|
p: 1,
|
||||||
|
borderRadius: 1,
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: 'grey.50',
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="body2" color="text.secondary" fontWeight="medium">
|
||||||
|
{x.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}:
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color={x === 'error' ? 'error' : 'text.primary'}
|
||||||
|
fontWeight="bold"
|
||||||
|
>
|
||||||
|
{typeof info[x] === 'number'
|
||||||
|
? info[x].toFixed(2)
|
||||||
|
: (info[x] != null ? String(info[x]) : 'N/A')
|
||||||
|
}
|
||||||
|
{x === 'elapsed' && ' ms'}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<Box display="flex" justifyContent="center" alignItems="center" minHeight="400px">
|
||||||
|
<CircularProgress />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<Box p={3}>
|
||||||
|
<Alert severity="error" sx={{ mb: 2 }}>
|
||||||
|
{error}
|
||||||
|
</Alert>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box p={3}>
|
||||||
|
<Box mb={4}>
|
||||||
|
<Typography variant="h4" component="h1" gutterBottom sx={{ fontWeight: 'bold' }}>
|
||||||
|
系统状态
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography variant="body1" color="text.secondary" paragraph>
|
||||||
|
查看系统各个组件的运行状态和性能指标
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{systemStatus ? (
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
{Object.keys(systemStatus).map((key) =>
|
||||||
|
renderSystemInfo(key, systemStatus[key as keyof ISystemStatus])
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
) : (
|
||||||
|
<Alert severity="info" sx={{ mt: 2 }}>
|
||||||
|
暂无系统状态数据
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,292 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
Typography,
|
||||||
|
Button,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Paper,
|
||||||
|
Chip,
|
||||||
|
IconButton,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
TextField,
|
||||||
|
Alert,
|
||||||
|
Divider,
|
||||||
|
Stack
|
||||||
|
} from '@mui/material';
|
||||||
|
import {
|
||||||
|
PersonAdd as PersonAddIcon,
|
||||||
|
Person as PersonIcon,
|
||||||
|
Group as GroupIcon,
|
||||||
|
Delete as DeleteIcon,
|
||||||
|
Check as CheckIcon,
|
||||||
|
Close as CloseIcon,
|
||||||
|
ExitToApp as ExitToAppIcon
|
||||||
|
} from '@mui/icons-material';
|
||||||
|
import { useTeamSetting } from '@/hooks/setting-hooks';
|
||||||
|
|
||||||
|
interface TenantUser {
|
||||||
|
id: string;
|
||||||
|
nickname: string;
|
||||||
|
email: string;
|
||||||
|
role: string;
|
||||||
|
update_date: string;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface JoinedTenant {
|
||||||
|
tenant_id: string;
|
||||||
|
nickname: string;
|
||||||
|
email: string;
|
||||||
|
role: string;
|
||||||
|
update_date: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TENANT_ROLES = {
|
||||||
|
Owner: 'owner',
|
||||||
|
Invite: 'invite',
|
||||||
|
Normal: 'normal',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
type TenantRole = (typeof TENANT_ROLES)[keyof typeof TENANT_ROLES]
|
||||||
|
|
||||||
function TeamsSetting() {
|
function TeamsSetting() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const {
|
||||||
|
userInfo,
|
||||||
|
tenantInfo,
|
||||||
|
tenantList,
|
||||||
|
tenantUsers,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
inviteUser,
|
||||||
|
deleteUser,
|
||||||
|
agreeTenant,
|
||||||
|
quitTenant
|
||||||
|
} = useTeamSetting();
|
||||||
|
|
||||||
|
const [inviteDialogOpen, setInviteDialogOpen] = useState(false);
|
||||||
|
const [inviteEmail, setInviteEmail] = useState('');
|
||||||
|
|
||||||
|
const handleInviteUser = async () => {
|
||||||
|
if (!inviteEmail.trim()) return;
|
||||||
|
|
||||||
|
const result = await inviteUser(inviteEmail);
|
||||||
|
if (result?.success) {
|
||||||
|
setInviteDialogOpen(false);
|
||||||
|
setInviteEmail('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteUser = async (userId: string) => {
|
||||||
|
await deleteUser(userId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAgreeTenant = async (tenantId: string) => {
|
||||||
|
await agreeTenant(tenantId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleQuitTenant = async (tenantId: string) => {
|
||||||
|
await quitTenant(tenantId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDate = (dateString: string) => {
|
||||||
|
return new Date(dateString).toLocaleString('zh-CN');
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRoleColor = (role: TenantRole) => {
|
||||||
|
if (!role) return 'primary';
|
||||||
|
// "error" | "primary" | "success" | "info" | "warning" | "secondary" | "default"
|
||||||
|
if (role === 'owner') return 'primary';
|
||||||
|
if (role === 'normal') return 'success';
|
||||||
|
if (role === 'invite') return 'warning';
|
||||||
|
return 'primary';
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Box sx={{ p: 3, maxWidth: 1200, mx: 'auto' }}>
|
||||||
<h1>Teams Setting</h1>
|
{/* 工作空间卡片 */}
|
||||||
</div>
|
<Card sx={{ mb: 3 }}>
|
||||||
|
<CardContent>
|
||||||
|
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||||
|
<Typography variant="h6" component="div">
|
||||||
|
{userInfo?.nickname} {t('setting.workspace')}
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
startIcon={<PersonAddIcon />}
|
||||||
|
onClick={() => setInviteDialogOpen(true)}
|
||||||
|
>
|
||||||
|
{t('setting.invite')}
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 团队成员表格 */}
|
||||||
|
<Card sx={{ mb: 3 }}>
|
||||||
|
<CardContent>
|
||||||
|
<Stack direction="row" alignItems="center" spacing={1} sx={{ mb: 2 }}>
|
||||||
|
<PersonIcon color="primary" />
|
||||||
|
<Typography variant="h6">{t('setting.teamMembers')}</Typography>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<TableContainer component={Paper} variant="outlined">
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell sx={{ fontWeight: 'bold' }}>{t('common.name')}</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 'bold' }}>{t('setting.email')}</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 'bold' }}>{t('setting.role')}</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 'bold' }}>{t('setting.updateDate')}</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 'bold' }}>{t('common.action')}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{tenantUsers.map((user) => (
|
||||||
|
<TableRow key={user.id}>
|
||||||
|
<TableCell>{user.nickname}</TableCell>
|
||||||
|
<TableCell>{user.email}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Chip
|
||||||
|
label={user.role}
|
||||||
|
color={getRoleColor(user.role)}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>{formatDate(user.update_date)}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{user.id !== userInfo?.id && (
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
color="error"
|
||||||
|
onClick={() => handleDeleteUser(user.id)}
|
||||||
|
>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 加入的团队表格 */}
|
||||||
|
<Card>
|
||||||
|
<CardContent>
|
||||||
|
<Stack direction="row" alignItems="center" spacing={1} sx={{ mb: 2 }}>
|
||||||
|
<GroupIcon color="primary" />
|
||||||
|
<Typography variant="h6">{t('setting.joinedTeams')}</Typography>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<TableContainer component={Paper} variant="outlined">
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell sx={{ fontWeight: 'bold' }}>{t('common.name')}</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 'bold' }}>{t('setting.email')}</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 'bold' }}>{t('setting.updateDate')}</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 'bold' }}>{t('common.action')}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{tenantList.map((tenant) => (
|
||||||
|
<TableRow key={tenant.tenant_id}>
|
||||||
|
<TableCell>{tenant.nickname}</TableCell>
|
||||||
|
<TableCell>{tenant.email}</TableCell>
|
||||||
|
<TableCell>{formatDate(tenant.update_date)}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{tenant.role === TENANT_ROLES.Invite ? (
|
||||||
|
<Stack direction="row" spacing={1}>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="contained"
|
||||||
|
color="success"
|
||||||
|
startIcon={<CheckIcon />}
|
||||||
|
onClick={() => handleAgreeTenant(tenant.tenant_id)}
|
||||||
|
>
|
||||||
|
{t('setting.agree')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
color="error"
|
||||||
|
startIcon={<CloseIcon />}
|
||||||
|
onClick={() => handleQuitTenant(tenant.tenant_id)}
|
||||||
|
>
|
||||||
|
{t('setting.refuse')}
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
) : (
|
||||||
|
tenant.role !== TENANT_ROLES.Owner && (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
color="warning"
|
||||||
|
startIcon={<ExitToAppIcon />}
|
||||||
|
onClick={() => handleQuitTenant(tenant.tenant_id)}
|
||||||
|
>
|
||||||
|
{t('setting.quit')}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 邀请用户对话框 */}
|
||||||
|
<Dialog open={inviteDialogOpen} onClose={() => setInviteDialogOpen(false)} maxWidth="sm" fullWidth>
|
||||||
|
<DialogTitle>{t('setting.invite')}</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
{error && (
|
||||||
|
<Alert severity="error" sx={{ mb: 2 }}>
|
||||||
|
{error}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
<TextField
|
||||||
|
autoFocus
|
||||||
|
margin="dense"
|
||||||
|
label={t('setting.email')}
|
||||||
|
type="email"
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
value={inviteEmail}
|
||||||
|
onChange={(e) => setInviteEmail(e.target.value)}
|
||||||
|
placeholder={t('setting.emailPlaceholder') || '请输入邀请用户的邮箱地址'}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setInviteDialogOpen(false)}>
|
||||||
|
{t('common.cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleInviteUser}
|
||||||
|
variant="contained"
|
||||||
|
disabled={loading || !inviteEmail.trim()}
|
||||||
|
>
|
||||||
|
{loading ? t('setting.inviting', 'inviting') : t('setting.invite')}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -114,6 +114,13 @@ const userService = {
|
|||||||
set_api_key: (data: ISetApiKeyRequestBody) => {
|
set_api_key: (data: ISetApiKeyRequestBody) => {
|
||||||
return request.post(api.set_api_key, data);
|
return request.post(api.set_api_key, data);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/* system status */
|
||||||
|
|
||||||
|
// 获取系统状态
|
||||||
|
system_status: () => {
|
||||||
|
return request.get(api.getSystemStatus);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default userService;
|
export default userService;
|
||||||
Reference in New Issue
Block a user