| | |
| | | useListContext, |
| | | useNotify, |
| | | useRefresh, |
| | | useTranslate, |
| | | } from "react-admin"; |
| | | import { |
| | | Box, |
| | |
| | | import DeleteOutlineOutlinedIcon from "@mui/icons-material/DeleteOutlineOutlined"; |
| | | import EditOutlinedIcon from "@mui/icons-material/EditOutlined"; |
| | | import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined"; |
| | | import CheckCircleOutlineRoundedIcon from "@mui/icons-material/CheckCircleOutlineRounded"; |
| | | import MyExportButton from "@/page/components/MyExportButton"; |
| | | import AiParamForm from "./AiParamForm"; |
| | | import AiConfigDialog from "../aiShared/AiConfigDialog"; |
| | | import AiRuntimeSummary from "../aiShared/AiRuntimeSummary"; |
| | | import { setAiParamDefault } from "@/api/ai/configCenter"; |
| | | |
| | | const filters = [ |
| | | <SearchInput source="condition" alwaysOn />, |
| | | <TextInput source="providerType" label="提供方类型" />, |
| | | <TextInput source="model" label="模型" />, |
| | | <TextInput source="providerType" label="ai.param.fields.providerType" />, |
| | | <TextInput source="model" label="ai.param.fields.model" />, |
| | | <SelectInput |
| | | source="status" |
| | | label="状态" |
| | | label="ai.param.fields.defaultStatus" |
| | | choices={[ |
| | | { id: "1", name: "common.enums.statusTrue" }, |
| | | { id: "0", name: "common.enums.statusFalse" }, |
| | | { id: "1", name: "ai.param.status.default" }, |
| | | { id: "0", name: "ai.param.status.nonDefault" }, |
| | | ]} |
| | | />, |
| | | ]; |
| | |
| | | return value.length > max ? `${value.slice(0, max)}...` : value; |
| | | }; |
| | | |
| | | const AiParamCards = ({ onView, onEdit, onDelete, deleting }) => { |
| | | const AiParamCards = ({ onView, onEdit, onDelete, onSetDefault, updatingDefaultId, deleting }) => { |
| | | const translate = useTranslate(); |
| | | const { data, isLoading } = useListContext(); |
| | | const records = useMemo(() => (Array.isArray(data) ? data : []), [data]); |
| | | |
| | |
| | | return ( |
| | | <Box px={2} py={6}> |
| | | <Card variant="outlined" sx={{ p: 3, textAlign: "center", borderStyle: "dashed" }}> |
| | | <Typography variant="subtitle1">暂无 AI 参数配置</Typography> |
| | | <Typography variant="subtitle1">{translate("ai.param.list.emptyTitle")}</Typography> |
| | | <Typography variant="body2" color="text.secondary" mt={1}> |
| | | 可以先新建一个 OpenAI 兼容模型参数卡片。 |
| | | {translate("ai.param.list.emptyDescription")} |
| | | </Typography> |
| | | </Card> |
| | | </Box> |
| | |
| | | <Chip |
| | | size="small" |
| | | color={record.statusBool ? "success" : "default"} |
| | | label={record.statusBool ? "启用" : "停用"} |
| | | label={translate(record.statusBool ? "ai.param.status.default" : "ai.param.status.nonDefault")} |
| | | /> |
| | | </Stack> |
| | | <Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap mt={1.5}> |
| | |
| | | size="small" |
| | | variant="outlined" |
| | | color={record.streamingEnabled ? "info" : "default"} |
| | | label={record.streamingEnabled ? "流式响应" : "非流式"} |
| | | label={translate(record.streamingEnabled ? "ai.param.list.streaming" : "ai.param.list.nonStreaming")} |
| | | /> |
| | | </Stack> |
| | | <Divider sx={{ my: 1.5 }} /> |
| | | <Typography variant="body2" color="text.secondary"> |
| | | Base URL |
| | | {translate("ai.param.fields.baseUrl")} |
| | | </Typography> |
| | | <Typography variant="body2" sx={{ mb: 1.5, wordBreak: "break-all" }}> |
| | | {truncateText(record.baseUrl, 120)} |
| | | </Typography> |
| | | <Grid container spacing={1}> |
| | | <Grid item xs={6}> |
| | | <Typography variant="caption" color="text.secondary">Temperature</Typography> |
| | | <Typography variant="caption" color="text.secondary">{translate("ai.param.fields.temperature")}</Typography> |
| | | <Typography variant="body2">{record.temperature ?? "--"}</Typography> |
| | | </Grid> |
| | | <Grid item xs={6}> |
| | | <Typography variant="caption" color="text.secondary">Top P</Typography> |
| | | <Typography variant="caption" color="text.secondary">{translate("ai.param.fields.topP")}</Typography> |
| | | <Typography variant="body2">{record.topP ?? "--"}</Typography> |
| | | </Grid> |
| | | <Grid item xs={6}> |
| | | <Typography variant="caption" color="text.secondary">Max Tokens</Typography> |
| | | <Typography variant="caption" color="text.secondary">{translate("ai.param.fields.maxTokens")}</Typography> |
| | | <Typography variant="body2">{record.maxTokens ?? "--"}</Typography> |
| | | </Grid> |
| | | <Grid item xs={6}> |
| | | <Typography variant="caption" color="text.secondary">Timeout</Typography> |
| | | <Typography variant="caption" color="text.secondary">{translate("ai.param.fields.timeoutMs")}</Typography> |
| | | <Typography variant="body2">{record.timeoutMs ?? "--"} ms</Typography> |
| | | </Grid> |
| | | </Grid> |
| | | <Typography variant="caption" color="text.secondary" display="block" mt={1.5}> |
| | | 备注 |
| | | </Typography> |
| | | <Typography variant="caption" color="text.secondary" display="block" mt={1.5}>{translate("common.field.memo")}</Typography> |
| | | <Typography variant="body2">{truncateText(record.memo)}</Typography> |
| | | </CardContent> |
| | | <CardActions sx={{ px: 2, pb: 2, pt: 0, justifyContent: "space-between" }}> |
| | | <Stack direction="row" spacing={1}> |
| | | <Button size="small" startIcon={<VisibilityOutlinedIcon />} onClick={() => onView(record.id)}> |
| | | 详情 |
| | | {translate("ai.common.detail")} |
| | | </Button> |
| | | <Button size="small" startIcon={<EditOutlinedIcon />} onClick={() => onEdit(record.id)}> |
| | | 编辑 |
| | | {translate("common.button.edit")} |
| | | </Button> |
| | | <Button |
| | | size="small" |
| | | color={record.statusBool ? "success" : "primary"} |
| | | startIcon={<CheckCircleOutlineRoundedIcon />} |
| | | onClick={() => onSetDefault(record)} |
| | | disabled={deleting || updatingDefaultId === record.id || record.statusBool} |
| | | > |
| | | {translate(record.statusBool ? "ai.param.actions.currentDefault" : "ai.param.actions.setDefault")} |
| | | </Button> |
| | | </Stack> |
| | | <Button |
| | |
| | | onClick={() => onDelete(record)} |
| | | disabled={deleting} |
| | | > |
| | | 删除 |
| | | {translate("ai.common.delete")} |
| | | </Button> |
| | | </CardActions> |
| | | </Card> |
| | |
| | | }; |
| | | |
| | | const AiParamList = () => { |
| | | const translate = useTranslate(); |
| | | const notify = useNotify(); |
| | | const refresh = useRefresh(); |
| | | const [deleteOne, { isPending: deleting }] = useDelete(); |
| | | const [dialogState, setDialogState] = useState({ open: false, mode: "create", recordId: null }); |
| | | const [updatingDefaultId, setUpdatingDefaultId] = useState(null); |
| | | |
| | | const openDialog = (mode, recordId = null) => setDialogState({ open: true, mode, recordId }); |
| | | const closeDialog = () => setDialogState({ open: false, mode: "create", recordId: null }); |
| | | |
| | | const handleDelete = (record) => { |
| | | if (!record?.id || !window.confirm(`确认删除“${record.name}”吗?`)) { |
| | | if (!record?.id || !window.confirm(translate("ai.common.confirmDelete", { name: record.name }))) { |
| | | return; |
| | | } |
| | | deleteOne( |
| | |
| | | { id: record.id }, |
| | | { |
| | | onSuccess: () => { |
| | | notify("删除成功"); |
| | | notify(translate("ai.common.deleteSuccess")); |
| | | refresh(); |
| | | }, |
| | | onError: (error) => { |
| | | notify(error?.message || "删除失败", { type: "error" }); |
| | | notify(error?.message || translate("ai.common.deleteFailed"), { type: "error" }); |
| | | }, |
| | | } |
| | | ); |
| | | }; |
| | | |
| | | const handleSetDefault = async (record) => { |
| | | if (!record?.id || record.statusBool) { |
| | | return; |
| | | } |
| | | setUpdatingDefaultId(record.id); |
| | | try { |
| | | await setAiParamDefault(record.id); |
| | | notify(translate("ai.param.actions.setDefaultSuccess")); |
| | | refresh(); |
| | | } catch (error) { |
| | | notify(error?.message || translate("ai.param.actions.setDefaultFailed"), { type: "error" }); |
| | | } finally { |
| | | setUpdatingDefaultId(null); |
| | | } |
| | | }; |
| | | |
| | | const dialogTitle = { |
| | | create: "新建 AI 参数", |
| | | edit: "编辑 AI 参数", |
| | | show: "查看 AI 参数详情", |
| | | create: translate("ai.param.dialog.create"), |
| | | edit: translate("ai.param.dialog.edit"), |
| | | show: translate("ai.param.dialog.show"), |
| | | }[dialogState.mode]; |
| | | |
| | | return ( |
| | |
| | | <TopToolbar> |
| | | <FilterButton /> |
| | | <Button variant="contained" startIcon={<AddRoundedIcon />} onClick={() => openDialog("create")}> |
| | | 新建 |
| | | {translate("ai.common.new")} |
| | | </Button> |
| | | <MyExportButton /> |
| | | </TopToolbar> |
| | | )} |
| | | > |
| | | <AiRuntimeSummary /> |
| | | <AiParamCards |
| | | onView={(id) => openDialog("show", id)} |
| | | onEdit={(id) => openDialog("edit", id)} |
| | | onDelete={handleDelete} |
| | | onSetDefault={handleSetDefault} |
| | | updatingDefaultId={updatingDefaultId} |
| | | deleting={deleting} |
| | | /> |
| | | </List> |