生产管理系统 - 应用生成、调度管理
This commit is contained in:
@@ -0,0 +1,517 @@
|
||||
/**
|
||||
* filekorolheader: 应用编辑对话框 - 模型应用编辑流程对话框
|
||||
* 功能:多步骤应用编辑流程、表单验证、应用更新
|
||||
* 路径:/ai-crop-model/model-application/generation/components/ApplicationEditDialog
|
||||
* 规范:遵循crop-x/docs/开发项目规范.md,使用shadcn语义化样式
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import { ApplicationGenerationState, ApplicationGenerationAction } from './ApplicationGenerationReducer';
|
||||
import { Application, ApplicationType, OutputFormat } from '@/types/ai-model';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import {
|
||||
Edit,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
FileText,
|
||||
Server,
|
||||
BarChart3,
|
||||
LineChart as LineChartIcon,
|
||||
PieChart as PieChartIcon,
|
||||
Table as TableIcon,
|
||||
Type,
|
||||
CheckCircle,
|
||||
AlertTriangle,
|
||||
} from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
interface ApplicationEditDialogProps {
|
||||
state: ApplicationGenerationState;
|
||||
dispatch: React.Dispatch<ApplicationGenerationAction>;
|
||||
}
|
||||
|
||||
export default function ApplicationEditDialog({ state, dispatch }: ApplicationEditDialogProps) {
|
||||
const { showEditDialog, editStep, editAppData, availableModels, inputFieldOptions, editingApp } = state;
|
||||
|
||||
const handleNextStep = () => {
|
||||
if (editStep === 1) {
|
||||
if (!editAppData.name || !editAppData.type || !editAppData.description) {
|
||||
toast.error('请填写完整的基本信息');
|
||||
return;
|
||||
}
|
||||
} else if (editStep === 2) {
|
||||
if (!editAppData.modelName || !editAppData.modelVersion) {
|
||||
toast.error('请选择模型');
|
||||
return;
|
||||
}
|
||||
} else if (editStep === 3) {
|
||||
if (editAppData.inputFields.length === 0) {
|
||||
toast.error('请选择至少一个输入字段');
|
||||
return;
|
||||
}
|
||||
} else if (editStep === 4) {
|
||||
if (!editAppData.outputFormat) {
|
||||
toast.error('请选择输出格式');
|
||||
return;
|
||||
}
|
||||
}
|
||||
dispatch({ type: 'SET_EDIT_STEP', payload: editStep + 1 });
|
||||
};
|
||||
|
||||
const handlePrevStep = () => {
|
||||
dispatch({ type: 'SET_EDIT_STEP', payload: editStep - 1 });
|
||||
};
|
||||
|
||||
const handleSaveEdit = () => {
|
||||
if (!editingApp) return;
|
||||
|
||||
const updatedApp: Application = {
|
||||
...editingApp,
|
||||
name: editAppData.name,
|
||||
type: editAppData.type as ApplicationType,
|
||||
description: editAppData.description,
|
||||
modelName: editAppData.modelName,
|
||||
modelVersion: editAppData.modelVersion,
|
||||
inputConfig: {
|
||||
fields: editAppData.inputFields,
|
||||
},
|
||||
outputConfig: {
|
||||
format: editAppData.outputFormat as OutputFormat,
|
||||
},
|
||||
};
|
||||
|
||||
dispatch({
|
||||
type: 'UPDATE_APPLICATION',
|
||||
payload: { id: editingApp.id, updates: updatedApp }
|
||||
});
|
||||
|
||||
dispatch({ type: 'SET_SHOW_EDIT_DIALOG', payload: false });
|
||||
dispatch({ type: 'SET_EDIT_STEP', payload: 1 });
|
||||
dispatch({ type: 'SET_EDITING_APP', payload: null });
|
||||
dispatch({ type: 'RESET_EDIT_APP_DATA' });
|
||||
|
||||
toast.success(`应用"${updatedApp.name}"更新成功!`);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
dispatch({ type: 'SET_SHOW_EDIT_DIALOG', payload: false });
|
||||
dispatch({ type: 'SET_EDIT_STEP', payload: 1 });
|
||||
dispatch({ type: 'SET_EDITING_APP', payload: null });
|
||||
dispatch({ type: 'RESET_EDIT_APP_DATA' });
|
||||
};
|
||||
|
||||
const getStepIcon = (step: number) => {
|
||||
switch (step) {
|
||||
case 1:
|
||||
return <FileText className="w-4 h-4" />;
|
||||
case 2:
|
||||
return <Server className="w-4 h-4" />;
|
||||
case 3:
|
||||
return <FileText className="w-4 h-4" />;
|
||||
case 4:
|
||||
return <BarChart3 className="w-4 h-4" />;
|
||||
default:
|
||||
return <CheckCircle className="w-4 h-4" />;
|
||||
}
|
||||
};
|
||||
|
||||
const getStepTitle = (step: number) => {
|
||||
switch (step) {
|
||||
case 1:
|
||||
return '填写基本信息';
|
||||
case 2:
|
||||
return '选择模型';
|
||||
case 3:
|
||||
return '配置输入';
|
||||
case 4:
|
||||
return '配置输出';
|
||||
case 5:
|
||||
return '预览保存';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={showEditDialog} onOpenChange={handleClose}>
|
||||
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Edit className="w-5 h-5 text-blue-600" />
|
||||
编辑应用 - {getStepTitle(editStep)}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
步骤 {editStep} / 5
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{/* 步骤指示器 */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
{[1, 2, 3, 4, 5].map((step) => (
|
||||
<div key={step} className="flex items-center">
|
||||
<div
|
||||
className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium ${
|
||||
step <= editStep
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'bg-gray-200 text-gray-600'
|
||||
}`}
|
||||
>
|
||||
{step < editStep ? <CheckCircle className="w-4 h-4" /> : getStepIcon(step)}
|
||||
</div>
|
||||
<span
|
||||
className={`ml-2 text-sm ${
|
||||
step <= editStep ? 'text-blue-600 font-medium' : 'text-gray-500'
|
||||
}`}
|
||||
>
|
||||
{getStepTitle(step)}
|
||||
</span>
|
||||
{step < 5 && (
|
||||
<ChevronRight className="w-4 h-4 mx-4 text-gray-400" />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 步骤内容 */}
|
||||
<div className="min-h-[400px]">
|
||||
{/* 步骤1: 基本信息 */}
|
||||
{editStep === 1 && (
|
||||
<div className="space-y-4">
|
||||
<Card className="p-4 bg-blue-50 dark:bg-blue-950 border-blue-200 dark:border-blue-800">
|
||||
<div className="flex items-center gap-2 text-blue-900 dark:text-blue-100">
|
||||
<FileText className="w-4 h-4" />
|
||||
<span className="text-sm">请填写应用的基本信息</span>
|
||||
</div>
|
||||
</Card>
|
||||
<div>
|
||||
<Label>应用名称 *</Label>
|
||||
<Input
|
||||
placeholder="请输入应用名称,如:智能灌溉策略生成"
|
||||
value={editAppData.name}
|
||||
onChange={(e) => dispatch({ type: 'SET_EDIT_APP_DATA', payload: { name: e.target.value } })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>应用类型 *</Label>
|
||||
<Select
|
||||
value={editAppData.type}
|
||||
onValueChange={(value) => dispatch({ type: 'SET_EDIT_APP_DATA', payload: { type: value } })}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择应用类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="智能灌溉">智能灌溉</SelectItem>
|
||||
<SelectItem value="病虫害预警">病虫害预警</SelectItem>
|
||||
<SelectItem value="施肥推荐">施肥推荐</SelectItem>
|
||||
<SelectItem value="产量预测">产量预测</SelectItem>
|
||||
<SelectItem value="生长监测">生长监测</SelectItem>
|
||||
<SelectItem value="其他">其他</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<Label>应用描述 *</Label>
|
||||
<Textarea
|
||||
placeholder="请详细描述应用的功能和使用场景"
|
||||
value={editAppData.description}
|
||||
onChange={(e) => dispatch({ type: 'SET_EDIT_APP_DATA', payload: { description: e.target.value } })}
|
||||
rows={4}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 步骤2: 选择模型 */}
|
||||
{editStep === 2 && (
|
||||
<div className="space-y-4">
|
||||
<Card className="p-4 bg-purple-50 dark:bg-purple-950 border-purple-200 dark:border-purple-800">
|
||||
<div className="flex items-center gap-2 text-purple-900 dark:text-purple-100">
|
||||
<Server className="w-4 h-4" />
|
||||
<span className="text-sm">选择用于此应用的AI模型</span>
|
||||
</div>
|
||||
</Card>
|
||||
<div>
|
||||
<Label>选择模型 *</Label>
|
||||
<div className="grid grid-cols-1 gap-3 mt-2">
|
||||
{availableModels.map((model) => (
|
||||
<div
|
||||
key={model.id}
|
||||
className={`p-4 border-2 rounded-lg cursor-pointer transition-all ${
|
||||
editAppData.modelName === model.name
|
||||
? 'border-blue-500 bg-blue-50'
|
||||
: 'border-gray-200 hover:border-blue-300'
|
||||
}`}
|
||||
onClick={() =>
|
||||
dispatch({
|
||||
type: 'SET_EDIT_APP_DATA',
|
||||
payload: { modelName: model.name, modelVersion: model.version }
|
||||
})
|
||||
}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="font-medium">{model.name}</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
版本: {model.version} | 类型: {model.type}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`w-2 h-2 rounded-full ${
|
||||
model.status === '运行中' ? 'bg-green-500' : 'bg-red-500'
|
||||
}`} />
|
||||
<span className="text-sm">{model.status}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 步骤3: 输入配置 */}
|
||||
{editStep === 3 && (
|
||||
<div className="space-y-4">
|
||||
<Card className="p-4 bg-green-50 dark:bg-green-950 border-green-200 dark:border-green-800">
|
||||
<div className="flex items-center gap-2 text-green-900 dark:text-green-100">
|
||||
<FileText className="w-4 h-4" />
|
||||
<span className="text-sm">配置应用的输入字段</span>
|
||||
</div>
|
||||
</Card>
|
||||
<div>
|
||||
<Label>输入字段 * (选择至少一个)</Label>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3 mt-2 max-h-64 overflow-y-auto p-2 border rounded-lg">
|
||||
{inputFieldOptions.map((field) => (
|
||||
<div key={field} className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id={`edit-${field}`}
|
||||
checked={editAppData.inputFields.includes(field)}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
dispatch({
|
||||
type: 'SET_EDIT_APP_DATA',
|
||||
payload: {
|
||||
inputFields: [...editAppData.inputFields, field]
|
||||
}
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'SET_EDIT_APP_DATA',
|
||||
payload: {
|
||||
inputFields: editAppData.inputFields.filter(f => f !== field)
|
||||
}
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor={`edit-${field}`} className="text-sm font-normal cursor-pointer">
|
||||
{field}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-2 text-sm text-muted-foreground">
|
||||
已选择 {editAppData.inputFields.length} 个字段
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 步骤4: 输出格式 */}
|
||||
{editStep === 4 && (
|
||||
<div className="space-y-4">
|
||||
<Card className="p-4 bg-orange-50 dark:bg-orange-950 border-orange-200 dark:border-orange-800">
|
||||
<div className="flex items-center gap-2 text-orange-900 dark:text-orange-100">
|
||||
<BarChart3 className="w-4 h-4" />
|
||||
<span className="text-sm">配置应用的输出格式</span>
|
||||
</div>
|
||||
</Card>
|
||||
<div>
|
||||
<Label>输出格式 *</Label>
|
||||
<div className="grid grid-cols-2 gap-3 mt-2">
|
||||
<div
|
||||
className={`p-4 border-2 rounded-lg cursor-pointer transition-all ${
|
||||
editAppData.outputFormat === '折线图'
|
||||
? 'border-blue-500 bg-blue-50'
|
||||
: 'border-gray-200 hover:border-blue-300'
|
||||
}`}
|
||||
onClick={() => dispatch({ type: 'SET_EDIT_APP_DATA', payload: { outputFormat: '折线图' } })}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
|
||||
<LineChartIcon className="w-5 h-5 text-blue-600" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium">折线图</div>
|
||||
<div className="text-xs text-muted-foreground">趋势数据可视化</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`p-4 border-2 rounded-lg cursor-pointer transition-all ${
|
||||
editAppData.outputFormat === '饼状图'
|
||||
? 'border-blue-500 bg-blue-50'
|
||||
: 'border-gray-200 hover:border-blue-300'
|
||||
}`}
|
||||
onClick={() => dispatch({ type: 'SET_EDIT_APP_DATA', payload: { outputFormat: '饼状图' } })}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center">
|
||||
<PieChartIcon className="w-5 h-5 text-green-600" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium">饼状图</div>
|
||||
<div className="text-xs text-muted-foreground">比例数据可视化</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`p-4 border-2 rounded-lg cursor-pointer transition-all ${
|
||||
editAppData.outputFormat === '表格'
|
||||
? 'border-blue-500 bg-blue-50'
|
||||
: 'border-gray-200 hover:border-blue-300'
|
||||
}`}
|
||||
onClick={() => dispatch({ type: 'SET_EDIT_APP_DATA', payload: { outputFormat: '表格' } })}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center">
|
||||
<TableIcon className="w-5 h-5 text-purple-600" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium">表格</div>
|
||||
<div className="text-xs text-muted-foreground">结构化数据展示</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`p-4 border-2 rounded-lg cursor-pointer transition-all ${
|
||||
editAppData.outputFormat === '文字'
|
||||
? 'border-blue-500 bg-blue-50'
|
||||
: 'border-gray-200 hover:border-blue-300'
|
||||
}`}
|
||||
onClick={() => dispatch({ type: 'SET_EDIT_APP_DATA', payload: { outputFormat: '文字' } })}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 bg-orange-100 rounded-lg flex items-center justify-center">
|
||||
<Type className="w-5 h-5 text-orange-600" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium">文字</div>
|
||||
<div className="text-xs text-muted-foreground">文本分析报告</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 步骤5: 确认保存 */}
|
||||
{editStep === 5 && (
|
||||
<div className="space-y-4">
|
||||
<Card className="p-4 bg-green-50 dark:bg-green-950 border-green-200 dark:border-green-800">
|
||||
<div className="flex items-center gap-2 text-green-900 dark:text-green-100">
|
||||
<CheckCircle className="w-4 h-4" />
|
||||
<span className="text-sm">确认应用信息并保存</span>
|
||||
</div>
|
||||
</Card>
|
||||
<Card className="p-6">
|
||||
<h3 className="font-semibold mb-4">应用信息确认</h3>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label className="text-sm text-muted-foreground">应用名称</Label>
|
||||
<div className="font-medium">{editAppData.name}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-sm text-muted-foreground">应用类型</Label>
|
||||
<div className="font-medium">{editAppData.type}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-sm text-muted-foreground">模型名称</Label>
|
||||
<div className="font-medium">{editAppData.modelName}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-sm text-muted-foreground">模型版本</Label>
|
||||
<div className="font-medium">{editAppData.modelVersion}</div>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<Label className="text-sm text-muted-foreground">应用描述</Label>
|
||||
<div className="text-sm">{editAppData.description}</div>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<Label className="text-sm text-muted-foreground">输入字段 ({editAppData.inputFields.length})</Label>
|
||||
<div className="flex flex-wrap gap-1 mt-1">
|
||||
{editAppData.inputFields.map((field) => (
|
||||
<span
|
||||
key={field}
|
||||
className="px-2 py-1 bg-blue-100 text-blue-800 text-xs rounded"
|
||||
>
|
||||
{field}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-sm text-muted-foreground">输出格式</Label>
|
||||
<div className="font-medium">{editAppData.outputFormat}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card className="p-4 bg-yellow-50 border-yellow-300">
|
||||
<div className="flex items-center gap-2 text-yellow-800">
|
||||
<AlertTriangle className="w-4 h-4" />
|
||||
<span className="text-sm">
|
||||
保存后将立即更新应用配置,请确认信息无误后保存。
|
||||
</span>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 底部按钮 */}
|
||||
<DialogFooter>
|
||||
<div className="flex justify-between w-full">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleClose}
|
||||
disabled={editStep === 5}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<div className="flex gap-2">
|
||||
{editStep > 1 && editStep < 5 && (
|
||||
<Button variant="outline" onClick={handlePrevStep}>
|
||||
<ChevronLeft className="w-4 h-4 mr-2" />
|
||||
上一步
|
||||
</Button>
|
||||
)}
|
||||
{editStep < 5 ? (
|
||||
<Button onClick={handleNextStep}>
|
||||
下一步
|
||||
<ChevronRight className="w-4 h-4 ml-2" />
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={handleSaveEdit} className="bg-green-600 hover:bg-green-700">
|
||||
<CheckCircle className="w-4 h-4 mr-2" />
|
||||
保存更改
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,505 @@
|
||||
/**
|
||||
* filekorolheader: 应用生成对话框 - 模型应用生成流程对话框
|
||||
* 功能:多步骤应用生成流程、表单验证、应用发布
|
||||
* 路径:/ai-crop-model/model-application/generation/components/ApplicationGenerateDialog
|
||||
* 规范:遵循crop-x/docs/开发项目规范.md,使用shadcn语义化样式
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import { ApplicationGenerationState, ApplicationGenerationAction } from './ApplicationGenerationReducer';
|
||||
import { Application, ApplicationType, OutputFormat } from '@/types/ai-model';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import {
|
||||
Sparkles,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
FileText,
|
||||
Server,
|
||||
BarChart3,
|
||||
LineChart as LineChartIcon,
|
||||
PieChart as PieChartIcon,
|
||||
Table as TableIcon,
|
||||
Type,
|
||||
CheckCircle,
|
||||
} from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
interface ApplicationGenerateDialogProps {
|
||||
state: ApplicationGenerationState;
|
||||
dispatch: React.Dispatch<ApplicationGenerationAction>;
|
||||
}
|
||||
|
||||
export default function ApplicationGenerateDialog({ state, dispatch }: ApplicationGenerateDialogProps) {
|
||||
const { showGenerateDialog, generateStep, newAppData, availableModels, inputFieldOptions } = state;
|
||||
|
||||
const handleNextStep = () => {
|
||||
if (generateStep === 1) {
|
||||
if (!newAppData.name || !newAppData.type || !newAppData.description) {
|
||||
toast.error('请填写完整的基本信息');
|
||||
return;
|
||||
}
|
||||
} else if (generateStep === 2) {
|
||||
if (!newAppData.modelName || !newAppData.modelVersion) {
|
||||
toast.error('请选择模型');
|
||||
return;
|
||||
}
|
||||
} else if (generateStep === 3) {
|
||||
if (newAppData.inputFields.length === 0) {
|
||||
toast.error('请选择至少一个输入字段');
|
||||
return;
|
||||
}
|
||||
} else if (generateStep === 4) {
|
||||
if (!newAppData.outputFormat) {
|
||||
toast.error('请选择输出格式');
|
||||
return;
|
||||
}
|
||||
}
|
||||
dispatch({ type: 'SET_GENERATE_STEP', payload: generateStep + 1 });
|
||||
};
|
||||
|
||||
const handlePrevStep = () => {
|
||||
dispatch({ type: 'SET_GENERATE_STEP', payload: generateStep - 1 });
|
||||
};
|
||||
|
||||
const handlePublishApp = () => {
|
||||
const newApp: Application = {
|
||||
id: `app-${Date.now()}`,
|
||||
name: newAppData.name,
|
||||
type: newAppData.type as ApplicationType,
|
||||
description: newAppData.description,
|
||||
modelName: newAppData.modelName,
|
||||
modelVersion: newAppData.modelVersion,
|
||||
inputConfig: {
|
||||
fields: newAppData.inputFields,
|
||||
},
|
||||
outputConfig: {
|
||||
format: newAppData.outputFormat as OutputFormat,
|
||||
},
|
||||
status: '已停止',
|
||||
createTime: new Date().toISOString().split('T')[0],
|
||||
runCount: 0,
|
||||
successRate: 0,
|
||||
avgExecutionTime: 0,
|
||||
};
|
||||
|
||||
dispatch({ type: 'ADD_APPLICATION', payload: newApp });
|
||||
dispatch({ type: 'SET_SHOW_GENERATE_DIALOG', payload: false });
|
||||
dispatch({ type: 'SET_GENERATE_STEP', payload: 1 });
|
||||
dispatch({ type: 'RESET_NEW_APP_DATA' });
|
||||
|
||||
toast.success('应用创建成功!');
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
dispatch({ type: 'SET_SHOW_GENERATE_DIALOG', payload: false });
|
||||
dispatch({ type: 'SET_GENERATE_STEP', payload: 1 });
|
||||
dispatch({ type: 'RESET_NEW_APP_DATA' });
|
||||
};
|
||||
|
||||
const getStepIcon = (step: number) => {
|
||||
switch (step) {
|
||||
case 1:
|
||||
return <FileText className="w-4 h-4" />;
|
||||
case 2:
|
||||
return <Server className="w-4 h-4" />;
|
||||
case 3:
|
||||
return <FileText className="w-4 h-4" />;
|
||||
case 4:
|
||||
return <BarChart3 className="w-4 h-4" />;
|
||||
default:
|
||||
return <CheckCircle className="w-4 h-4" />;
|
||||
}
|
||||
};
|
||||
|
||||
const getStepTitle = (step: number) => {
|
||||
switch (step) {
|
||||
case 1:
|
||||
return '基本信息';
|
||||
case 2:
|
||||
return '选择模型';
|
||||
case 3:
|
||||
return '输入配置';
|
||||
case 4:
|
||||
return '输出格式';
|
||||
case 5:
|
||||
return '确认发布';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={showGenerateDialog} onOpenChange={handleClose}>
|
||||
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Sparkles className="w-5 h-5 text-blue-600" />
|
||||
创建应用 - {['填写基本信息', '选择模型', '配置输入', '配置输出', '预览发布'][generateStep - 1]}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
步骤 {generateStep} / 5
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{/* 步骤指示器 */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
{[1, 2, 3, 4, 5].map((step) => (
|
||||
<div key={step} className="flex items-center">
|
||||
<div
|
||||
className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium ${
|
||||
step <= generateStep
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'bg-gray-200 text-gray-600'
|
||||
}`}
|
||||
>
|
||||
{step < generateStep ? <CheckCircle className="w-4 h-4" /> : getStepIcon(step)}
|
||||
</div>
|
||||
<span
|
||||
className={`ml-2 text-sm ${
|
||||
step <= generateStep ? 'text-blue-600 font-medium' : 'text-gray-500'
|
||||
}`}
|
||||
>
|
||||
{getStepTitle(step)}
|
||||
</span>
|
||||
{step < 5 && (
|
||||
<ChevronRight className="w-4 h-4 mx-4 text-gray-400" />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 步骤内容 */}
|
||||
<div className="min-h-[400px]">
|
||||
{/* 步骤1: 基本信息 */}
|
||||
{generateStep === 1 && (
|
||||
<div className="space-y-4">
|
||||
<Card className="p-4 bg-blue-50 dark:bg-blue-950 border-blue-200 dark:border-blue-800">
|
||||
<div className="flex items-center gap-2 text-blue-900 dark:text-blue-100">
|
||||
<FileText className="w-4 h-4" />
|
||||
<span className="text-sm">请填写应用的基本信息</span>
|
||||
</div>
|
||||
</Card>
|
||||
<div>
|
||||
<Label>应用名称 *</Label>
|
||||
<Input
|
||||
placeholder="请输入应用名称,如:智能灌溉策略生成"
|
||||
value={newAppData.name}
|
||||
onChange={(e) => dispatch({ type: 'SET_NEW_APP_DATA', payload: { name: e.target.value } })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>应用类型 *</Label>
|
||||
<Select
|
||||
value={newAppData.type}
|
||||
onValueChange={(value) => dispatch({ type: 'SET_NEW_APP_DATA', payload: { type: value } })}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择应用类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="智能灌溉">智能灌溉</SelectItem>
|
||||
<SelectItem value="病虫害预警">病虫害预警</SelectItem>
|
||||
<SelectItem value="施肥推荐">施肥推荐</SelectItem>
|
||||
<SelectItem value="产量预测">产量预测</SelectItem>
|
||||
<SelectItem value="生长监测">生长监测</SelectItem>
|
||||
<SelectItem value="其他">其他</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<Label>应用描述 *</Label>
|
||||
<Textarea
|
||||
placeholder="请详细描述应用的功能和使用场景"
|
||||
value={newAppData.description}
|
||||
onChange={(e) => dispatch({ type: 'SET_NEW_APP_DATA', payload: { description: e.target.value } })}
|
||||
rows={4}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 步骤2: 选择模型 */}
|
||||
{generateStep === 2 && (
|
||||
<div className="space-y-4">
|
||||
<Card className="p-4 bg-purple-50 dark:bg-purple-950 border-purple-200 dark:border-purple-800">
|
||||
<div className="flex items-center gap-2 text-purple-900 dark:text-purple-100">
|
||||
<Server className="w-4 h-4" />
|
||||
<span className="text-sm">选择用于此应用的AI模型</span>
|
||||
</div>
|
||||
</Card>
|
||||
<div>
|
||||
<Label>选择模型 *</Label>
|
||||
<div className="grid grid-cols-1 gap-3 mt-2">
|
||||
{availableModels.map((model) => (
|
||||
<div
|
||||
key={model.id}
|
||||
className={`p-4 border-2 rounded-lg cursor-pointer transition-all ${
|
||||
newAppData.modelName === model.name
|
||||
? 'border-blue-500 bg-blue-50 dark:bg-blue-950'
|
||||
: 'border-gray-200 dark:border-gray-700 hover:border-blue-300 dark:hover:border-blue-600'
|
||||
}`}
|
||||
onClick={() =>
|
||||
dispatch({
|
||||
type: 'SET_NEW_APP_DATA',
|
||||
payload: { modelName: model.name, modelVersion: model.version }
|
||||
})
|
||||
}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="font-medium">{model.name}</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
版本: {model.version} | 类型: {model.type}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`w-2 h-2 rounded-full ${
|
||||
model.status === '运行中' ? 'bg-green-500' : 'bg-red-500'
|
||||
}`} />
|
||||
<span className="text-sm">{model.status}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 步骤3: 输入配置 */}
|
||||
{generateStep === 3 && (
|
||||
<div className="space-y-4">
|
||||
<Card className="p-4 bg-green-50 dark:bg-green-950 border-green-200 dark:border-green-800">
|
||||
<div className="flex items-center gap-2 text-green-900 dark:text-green-100">
|
||||
<FileText className="w-4 h-4" />
|
||||
<span className="text-sm">配置应用的输入字段</span>
|
||||
</div>
|
||||
</Card>
|
||||
<div>
|
||||
<Label>输入字段 * (选择至少一个)</Label>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3 mt-2 max-h-64 overflow-y-auto p-2 border rounded-lg">
|
||||
{inputFieldOptions.map((field) => (
|
||||
<div key={field} className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id={field}
|
||||
checked={newAppData.inputFields.includes(field)}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
dispatch({
|
||||
type: 'SET_NEW_APP_DATA',
|
||||
payload: {
|
||||
inputFields: [...newAppData.inputFields, field]
|
||||
}
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'SET_NEW_APP_DATA',
|
||||
payload: {
|
||||
inputFields: newAppData.inputFields.filter(f => f !== field)
|
||||
}
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor={field} className="text-sm font-normal cursor-pointer">
|
||||
{field}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-2 text-sm text-muted-foreground">
|
||||
已选择 {newAppData.inputFields.length} 个字段
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 步骤4: 输出格式 */}
|
||||
{generateStep === 4 && (
|
||||
<div className="space-y-4">
|
||||
<Card className="p-4 bg-orange-50 dark:bg-orange-950 border-orange-200 dark:border-orange-800">
|
||||
<div className="flex items-center gap-2 text-orange-900 dark:text-orange-100">
|
||||
<BarChart3 className="w-4 h-4" />
|
||||
<span className="text-sm">配置应用的输出格式</span>
|
||||
</div>
|
||||
</Card>
|
||||
<div>
|
||||
<Label>输出格式 *</Label>
|
||||
<div className="grid grid-cols-2 gap-3 mt-2">
|
||||
<div
|
||||
className={`p-4 border-2 rounded-lg cursor-pointer transition-all ${
|
||||
newAppData.outputFormat === '折线图'
|
||||
? 'border-blue-500 bg-blue-50 dark:bg-blue-950'
|
||||
: 'border-gray-200 dark:border-gray-700 hover:border-blue-300 dark:hover:border-blue-600'
|
||||
}`}
|
||||
onClick={() => dispatch({ type: 'SET_NEW_APP_DATA', payload: { outputFormat: '折线图' } })}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 bg-blue-100 dark:bg-blue-900 rounded-lg flex items-center justify-center">
|
||||
<LineChartIcon className="w-5 h-5 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium">折线图</div>
|
||||
<div className="text-xs text-muted-foreground">趋势数据可视化</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`p-4 border-2 rounded-lg cursor-pointer transition-all ${
|
||||
newAppData.outputFormat === '饼状图'
|
||||
? 'border-blue-500 bg-blue-50 dark:bg-blue-950'
|
||||
: 'border-gray-200 dark:border-gray-700 hover:border-blue-300 dark:hover:border-blue-600'
|
||||
}`}
|
||||
onClick={() => dispatch({ type: 'SET_NEW_APP_DATA', payload: { outputFormat: '饼状图' } })}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 bg-green-100 dark:bg-green-900 rounded-lg flex items-center justify-center">
|
||||
<PieChartIcon className="w-5 h-5 text-green-600 dark:text-green-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium">饼状图</div>
|
||||
<div className="text-xs text-muted-foreground">比例数据可视化</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`p-4 border-2 rounded-lg cursor-pointer transition-all ${
|
||||
newAppData.outputFormat === '表格'
|
||||
? 'border-blue-500 bg-blue-50 dark:bg-blue-950'
|
||||
: 'border-gray-200 dark:border-gray-700 hover:border-blue-300 dark:hover:border-blue-600'
|
||||
}`}
|
||||
onClick={() => dispatch({ type: 'SET_NEW_APP_DATA', payload: { outputFormat: '表格' } })}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 bg-purple-100 dark:bg-purple-900 rounded-lg flex items-center justify-center">
|
||||
<TableIcon className="w-5 h-5 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium">表格</div>
|
||||
<div className="text-xs text-muted-foreground">结构化数据展示</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`p-4 border-2 rounded-lg cursor-pointer transition-all ${
|
||||
newAppData.outputFormat === '文字'
|
||||
? 'border-blue-500 bg-blue-50 dark:bg-blue-950'
|
||||
: 'border-gray-200 dark:border-gray-700 hover:border-blue-300 dark:hover:border-blue-600'
|
||||
}`}
|
||||
onClick={() => dispatch({ type: 'SET_NEW_APP_DATA', payload: { outputFormat: '文字' } })}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 bg-orange-100 dark:bg-orange-900 rounded-lg flex items-center justify-center">
|
||||
<Type className="w-5 h-5 text-orange-600 dark:text-orange-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium">文字</div>
|
||||
<div className="text-xs text-muted-foreground">文本分析报告</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 步骤5: 确认发布 */}
|
||||
{generateStep === 5 && (
|
||||
<div className="space-y-4">
|
||||
<Card className="p-4 bg-green-50 dark:bg-green-950 border-green-200 dark:border-green-800">
|
||||
<div className="flex items-center gap-2 text-green-900 dark:text-green-100">
|
||||
<CheckCircle className="w-4 h-4" />
|
||||
<span className="text-sm">确认应用信息并发布</span>
|
||||
</div>
|
||||
</Card>
|
||||
<Card className="p-6">
|
||||
<h3 className="font-semibold mb-4">应用信息确认</h3>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label className="text-sm text-muted-foreground">应用名称</Label>
|
||||
<div className="font-medium">{newAppData.name}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-sm text-muted-foreground">应用类型</Label>
|
||||
<div className="font-medium">{newAppData.type}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-sm text-muted-foreground">模型名称</Label>
|
||||
<div className="font-medium">{newAppData.modelName}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-sm text-muted-foreground">模型版本</Label>
|
||||
<div className="font-medium">{newAppData.modelVersion}</div>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<Label className="text-sm text-muted-foreground">应用描述</Label>
|
||||
<div className="text-sm">{newAppData.description}</div>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<Label className="text-sm text-muted-foreground">输入字段 ({newAppData.inputFields.length})</Label>
|
||||
<div className="flex flex-wrap gap-1 mt-1">
|
||||
{newAppData.inputFields.map((field) => (
|
||||
<span
|
||||
key={field}
|
||||
className="px-2 py-1 bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 text-xs rounded"
|
||||
>
|
||||
{field}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-sm text-muted-foreground">输出格式</Label>
|
||||
<div className="font-medium">{newAppData.outputFormat}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 底部按钮 */}
|
||||
<DialogFooter>
|
||||
<div className="flex justify-between w-full">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleClose}
|
||||
disabled={generateStep === 5}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<div className="flex gap-2">
|
||||
{generateStep > 1 && generateStep < 5 && (
|
||||
<Button variant="outline" onClick={handlePrevStep}>
|
||||
<ChevronLeft className="w-4 h-4 mr-2" />
|
||||
上一步
|
||||
</Button>
|
||||
)}
|
||||
{generateStep < 5 ? (
|
||||
<Button onClick={handleNextStep}>
|
||||
下一步
|
||||
<ChevronRight className="w-4 h-4 ml-2" />
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={handlePublishApp} className="bg-green-600 hover:bg-green-700">
|
||||
<CheckCircle className="w-4 h-4 mr-2" />
|
||||
发布应用
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
/**
|
||||
* filekorolheader: 应用生成状态管理 - 模型应用生成中心状态管理
|
||||
* 功能:应用状态管理、生成流程控制、对话框状态管理
|
||||
* 路径:/ai-crop-model/model-application/generation/components/ApplicationGenerationReducer
|
||||
* 规范:遵循crop-x/docs/开发项目规范.md,使用TypeScript类型安全
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import { Application } from '@/types/ai-model';
|
||||
|
||||
export interface ApplicationGenerationState {
|
||||
applications: Application[];
|
||||
showGenerateDialog: boolean;
|
||||
generateStep: number;
|
||||
newAppData: {
|
||||
name: string;
|
||||
type: string;
|
||||
description: string;
|
||||
modelName: string;
|
||||
modelVersion: string;
|
||||
inputFields: string[];
|
||||
outputFormat: string;
|
||||
};
|
||||
showRunDialog: boolean;
|
||||
runningApp: Application | null;
|
||||
inputData: Record<string, string>;
|
||||
isRunning: boolean;
|
||||
runResult: any;
|
||||
availableModels: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
version: string;
|
||||
type: string;
|
||||
status: string;
|
||||
}>;
|
||||
inputFieldOptions: string[];
|
||||
dataSourceOptions: string[];
|
||||
iotDeviceOptions: string[];
|
||||
showEditDialog: boolean;
|
||||
editingApp: Application | null;
|
||||
editStep: number;
|
||||
editAppData: {
|
||||
name: string;
|
||||
type: string;
|
||||
description: string;
|
||||
modelName: string;
|
||||
modelVersion: string;
|
||||
inputFields: string[];
|
||||
outputFormat: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type ApplicationGenerationAction =
|
||||
| { type: 'SET_APPLICATIONS'; payload: Application[] }
|
||||
| { type: 'ADD_APPLICATION'; payload: Application }
|
||||
| { type: 'UPDATE_APPLICATION'; payload: { id: string; updates: Partial<Application> } }
|
||||
| { type: 'SET_SHOW_GENERATE_DIALOG'; payload: boolean }
|
||||
| { type: 'SET_GENERATE_STEP'; payload: number }
|
||||
| { type: 'SET_NEW_APP_DATA'; payload: Partial<ApplicationGenerationState['newAppData']> }
|
||||
| { type: 'RESET_NEW_APP_DATA' }
|
||||
| { type: 'SET_SHOW_RUN_DIALOG'; payload: boolean }
|
||||
| { type: 'SET_RUNNING_APP'; payload: Application | null }
|
||||
| { type: 'SET_INPUT_DATA'; payload: Record<string, string> }
|
||||
| { type: 'SET_IS_RUNNING'; payload: boolean }
|
||||
| { type: 'SET_RUN_RESULT'; payload: any }
|
||||
| { type: 'TOGGLE_APPLICATION_STATUS'; payload: string }
|
||||
| { type: 'SET_SHOW_EDIT_DIALOG'; payload: boolean }
|
||||
| { type: 'SET_EDITING_APP'; payload: Application | null }
|
||||
| { type: 'SET_EDIT_STEP'; payload: number }
|
||||
| { type: 'SET_EDIT_APP_DATA'; payload: Partial<ApplicationGenerationState['editAppData']> }
|
||||
| { type: 'RESET_EDIT_APP_DATA' };
|
||||
|
||||
export function ApplicationGenerationReducer(
|
||||
state: ApplicationGenerationState,
|
||||
action: ApplicationGenerationAction
|
||||
): ApplicationGenerationState {
|
||||
switch (action.type) {
|
||||
case 'SET_APPLICATIONS':
|
||||
return { ...state, applications: action.payload };
|
||||
|
||||
case 'ADD_APPLICATION':
|
||||
return {
|
||||
...state,
|
||||
applications: [...state.applications, action.payload]
|
||||
};
|
||||
|
||||
case 'UPDATE_APPLICATION':
|
||||
return {
|
||||
...state,
|
||||
applications: state.applications.map(app =>
|
||||
app.id === action.payload.id
|
||||
? { ...app, ...action.payload.updates }
|
||||
: app
|
||||
)
|
||||
};
|
||||
|
||||
case 'TOGGLE_APPLICATION_STATUS':
|
||||
return {
|
||||
...state,
|
||||
applications: state.applications.map(app =>
|
||||
app.id === action.payload
|
||||
? { ...app, status: app.status === '运行中' ? '已停止' : '运行中' }
|
||||
: app
|
||||
)
|
||||
};
|
||||
|
||||
case 'SET_SHOW_GENERATE_DIALOG':
|
||||
return { ...state, showGenerateDialog: action.payload };
|
||||
|
||||
case 'SET_GENERATE_STEP':
|
||||
return { ...state, generateStep: action.payload };
|
||||
|
||||
case 'SET_NEW_APP_DATA':
|
||||
return {
|
||||
...state,
|
||||
newAppData: { ...state.newAppData, ...action.payload }
|
||||
};
|
||||
|
||||
case 'RESET_NEW_APP_DATA':
|
||||
return {
|
||||
...state,
|
||||
newAppData: {
|
||||
name: '',
|
||||
type: '',
|
||||
description: '',
|
||||
modelName: '',
|
||||
modelVersion: '',
|
||||
inputFields: [],
|
||||
outputFormat: '',
|
||||
}
|
||||
};
|
||||
|
||||
case 'SET_SHOW_RUN_DIALOG':
|
||||
return { ...state, showRunDialog: action.payload };
|
||||
|
||||
case 'SET_RUNNING_APP':
|
||||
return { ...state, runningApp: action.payload };
|
||||
|
||||
case 'SET_INPUT_DATA':
|
||||
return { ...state, inputData: action.payload };
|
||||
|
||||
case 'SET_IS_RUNNING':
|
||||
return { ...state, isRunning: action.payload };
|
||||
|
||||
case 'SET_RUN_RESULT':
|
||||
return { ...state, runResult: action.payload };
|
||||
|
||||
case 'SET_SHOW_EDIT_DIALOG':
|
||||
return { ...state, showEditDialog: action.payload };
|
||||
|
||||
case 'SET_EDITING_APP':
|
||||
return { ...state, editingApp: action.payload };
|
||||
|
||||
case 'SET_EDIT_STEP':
|
||||
return { ...state, editStep: action.payload };
|
||||
|
||||
case 'SET_EDIT_APP_DATA':
|
||||
return {
|
||||
...state,
|
||||
editAppData: { ...state.editAppData, ...action.payload }
|
||||
};
|
||||
|
||||
case 'RESET_EDIT_APP_DATA':
|
||||
return {
|
||||
...state,
|
||||
editAppData: {
|
||||
name: '',
|
||||
type: '',
|
||||
description: '',
|
||||
modelName: '',
|
||||
modelVersion: '',
|
||||
inputFields: [],
|
||||
outputFormat: '',
|
||||
}
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
/**
|
||||
* filekorolheader: 应用列表组件 - 模型应用列表展示
|
||||
* 功能:应用列表展示、状态管理、操作按钮
|
||||
* 路径:/ai-crop-model/model-application/generation/components/ApplicationList
|
||||
* 规范:遵循crop-x/docs/开发项目规范.md,使用shadcn语义化样式
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import { ApplicationGenerationState, ApplicationGenerationAction } from './ApplicationGenerationReducer';
|
||||
import { Application } from '@/types/ai-model';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import {
|
||||
PlayCircle,
|
||||
PauseCircle,
|
||||
StopCircle,
|
||||
Eye,
|
||||
Edit,
|
||||
Clock,
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
AlertTriangle,
|
||||
Droplets,
|
||||
Bug,
|
||||
Sprout,
|
||||
TrendingUp,
|
||||
Activity,
|
||||
Server,
|
||||
Terminal,
|
||||
LineChart as LineChartIcon,
|
||||
PieChart as PieChartIcon,
|
||||
Table as TableIcon,
|
||||
Type,
|
||||
Rocket,
|
||||
} from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
interface ApplicationListProps {
|
||||
state: ApplicationGenerationState;
|
||||
dispatch: React.Dispatch<ApplicationGenerationAction>;
|
||||
}
|
||||
|
||||
export default function ApplicationList({ state, dispatch }: ApplicationListProps) {
|
||||
// 辅助函数
|
||||
const getAppTypeIcon = (type: string) => {
|
||||
switch (type) {
|
||||
case '智能灌溉': return <Droplets className="w-4 h-4 text-green-600" />;
|
||||
case '病虫害预警': return <Bug className="w-4 h-4 text-orange-600" />;
|
||||
case '施肥推荐': return <Sprout className="w-4 h-4 text-purple-600" />;
|
||||
case '产量预测': return <TrendingUp className="w-4 h-4 text-blue-600" />;
|
||||
case '生长监测': return <Activity className="w-4 h-4 text-cyan-600" />;
|
||||
default: return <Server className="w-4 h-4 text-gray-600" />;
|
||||
}
|
||||
};
|
||||
|
||||
const getOutputFormatIcon = (format: string) => {
|
||||
switch (format) {
|
||||
case '折线图': return <LineChartIcon className="w-4 h-4 text-blue-600" />;
|
||||
case '饼状图': return <PieChartIcon className="w-4 h-4 text-green-600" />;
|
||||
case '表格': return <TableIcon className="w-4 h-4 text-purple-600" />;
|
||||
case '文字': return <Type className="w-4 h-4 text-gray-600" />;
|
||||
default: return <Server className="w-4 h-4 text-gray-600" />;
|
||||
}
|
||||
};
|
||||
|
||||
const handleRunApplication = (app: Application) => {
|
||||
dispatch({ type: 'SET_RUNNING_APP', payload: app });
|
||||
dispatch({ type: 'SET_SHOW_RUN_DIALOG', payload: true });
|
||||
dispatch({ type: 'SET_INPUT_DATA', payload: {} });
|
||||
dispatch({ type: 'SET_RUN_RESULT', payload: null });
|
||||
};
|
||||
|
||||
const handleToggleStatus = (appId: string) => {
|
||||
dispatch({ type: 'TOGGLE_APPLICATION_STATUS', payload: appId });
|
||||
const app = state.applications.find(a => a.id === appId);
|
||||
toast.success(`应用已${app?.status === '运行中' ? '停止' : '启动'}`);
|
||||
};
|
||||
|
||||
const handleEditApp = (app: Application) => {
|
||||
dispatch({ type: 'SET_EDITING_APP', payload: app });
|
||||
dispatch({
|
||||
type: 'SET_EDIT_APP_DATA',
|
||||
payload: {
|
||||
name: app.name,
|
||||
type: app.type,
|
||||
description: app.description,
|
||||
modelName: app.modelName,
|
||||
modelVersion: app.modelVersion,
|
||||
inputFields: app.inputConfig.fields,
|
||||
outputFormat: app.outputConfig.format,
|
||||
}
|
||||
});
|
||||
dispatch({ type: 'SET_EDIT_STEP', payload: 1 });
|
||||
dispatch({ type: 'SET_SHOW_EDIT_DIALOG', payload: true });
|
||||
};
|
||||
|
||||
const getStatusIcon = (status: string) => {
|
||||
switch (status) {
|
||||
case '运行中':
|
||||
return <PlayCircle className="w-4 h-4 text-green-600" />;
|
||||
case '已停止':
|
||||
return <StopCircle className="w-4 h-4 text-gray-600" />;
|
||||
case '故障':
|
||||
return <XCircle className="w-4 h-4 text-red-600" />;
|
||||
default:
|
||||
return <AlertCircle className="w-4 h-4 text-gray-600" />;
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case '运行中':
|
||||
return 'bg-green-100 text-green-700 border-green-300';
|
||||
case '已停止':
|
||||
return 'bg-gray-100 text-gray-700 border-gray-300';
|
||||
case '故障':
|
||||
return 'bg-red-100 text-red-700 border-red-300';
|
||||
default:
|
||||
return 'bg-gray-100 text-gray-700 border-gray-300';
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
switch (status) {
|
||||
case '运行中':
|
||||
return (
|
||||
<Badge className="bg-green-100 text-green-800 border-green-200 font-light">
|
||||
<CheckCircle className="w-3 h-3 mr-1" />
|
||||
运行中
|
||||
</Badge>
|
||||
);
|
||||
case '已停止':
|
||||
return (
|
||||
<Badge className="bg-red-100 text-red-800 border-red-200 font-light">
|
||||
<XCircle className="w-3 h-3 mr-1" />
|
||||
已停止
|
||||
</Badge>
|
||||
);
|
||||
case '故障':
|
||||
return (
|
||||
<Badge className="bg-orange-100 text-orange-800 border-orange-200 font-light">
|
||||
<AlertTriangle className="w-3 h-3 mr-1" />
|
||||
故障
|
||||
</Badge>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<Badge variant="outline" className="font-light">
|
||||
<Clock className="w-3 h-3 mr-1" />
|
||||
未知
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const getTypeColor = (type: string) => {
|
||||
switch (type) {
|
||||
case '智能灌溉':
|
||||
return 'bg-blue-50 dark:bg-blue-950 border-blue-200 dark:border-blue-800';
|
||||
case '病虫害预警':
|
||||
return 'bg-red-50 dark:bg-red-950 border-red-200 dark:border-red-800';
|
||||
case '施肥推荐':
|
||||
return 'bg-green-50 dark:bg-green-950 border-green-200 dark:border-green-800';
|
||||
case '产量预测':
|
||||
return 'bg-purple-50 dark:bg-purple-950 border-purple-200 dark:border-purple-800';
|
||||
case '生长监测':
|
||||
return 'bg-orange-50 dark:bg-orange-950 border-orange-200 dark:border-orange-800';
|
||||
default:
|
||||
return 'bg-gray-50 dark:bg-gray-950 border-gray-200 dark:border-gray-800';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3 className="mb-4 flex items-center gap-2">
|
||||
<Rocket className="w-5 h-5 text-blue-600" />
|
||||
我的应用
|
||||
</h3>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{state.applications.map((app) => (
|
||||
<Card key={app.id} className="p-6 hover:shadow-lg transition-shadow">
|
||||
<div className="space-y-4">
|
||||
{/* 头部 */}
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-12 h-12 bg-blue-50 rounded-lg flex items-center justify-center">
|
||||
{getAppTypeIcon(app.type)}
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-base">{app.name}</h4>
|
||||
<Badge variant="outline" className="mt-1">{app.type}</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 描述 */}
|
||||
<p className="text-sm text-muted-foreground line-clamp-2">
|
||||
{app.description}
|
||||
</p>
|
||||
{/* 模型信息 */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<Server className="w-4 h-4 text-gray-500" />
|
||||
<span className="text-muted-foreground">模型:</span>
|
||||
<span>{app.modelName}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<Terminal className="w-4 h-4 text-gray-500" />
|
||||
<span className="text-muted-foreground">版本:</span>
|
||||
<span>{app.modelVersion}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* 输入字段 */}
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-2">输入字段:</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{app.inputConfig.fields.slice(0, 3).map((field, idx) => (
|
||||
<Badge key={idx} variant="outline" className="text-xs">
|
||||
{field}
|
||||
</Badge>
|
||||
))}
|
||||
{app.inputConfig.fields.length > 3 && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
+{app.inputConfig.fields.length - 3}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* 输出格式 */}
|
||||
<div className="flex items-center gap-2">
|
||||
{getOutputFormatIcon(app.outputConfig.format)}
|
||||
<span className="text-sm text-muted-foreground">输出:</span>
|
||||
<Badge className="bg-purple-100 text-purple-700 border-purple-300">
|
||||
{app.outputConfig.format}
|
||||
</Badge>
|
||||
</div>
|
||||
{/* 统计信息 */}
|
||||
<div className="pt-4 border-t">
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">执行次数</p>
|
||||
<p className="text-lg">{app.runCount}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">成功率</p>
|
||||
<p className="text-lg text-green-600">{app.successRate}%</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 状态和操作 */}
|
||||
<div className="space-y-3 pt-4 border-t">
|
||||
<div className="flex items-center gap-2">
|
||||
{getStatusIcon(app.status)}
|
||||
<Badge className={getStatusColor(app.status)}>{app.status}</Badge>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
{/* 运行按钮 - 仅在非停止状态显示 */}
|
||||
{app.status !== '已停止' && (
|
||||
<Button
|
||||
size="sm"
|
||||
className="flex-1 bg-blue-600 hover:bg-blue-700"
|
||||
onClick={() => handleRunApplication(app)}
|
||||
>
|
||||
<PlayCircle className="w-3 h-3 mr-1" />
|
||||
运行
|
||||
</Button>
|
||||
)}
|
||||
{/* 停止/启动按钮 */}
|
||||
{app.status === '运行中' ? (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className={app.status === '已停止' ? 'flex-1' : ''}
|
||||
onClick={() => handleToggleStatus(app.id)}
|
||||
>
|
||||
<PauseCircle className="w-3 h-3" />
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className={app.status === '已停止' ? 'flex-1' : ''}
|
||||
onClick={() => handleToggleStatus(app.id)}
|
||||
>
|
||||
<PlayCircle className="w-3 h-3" />
|
||||
</Button>
|
||||
)}
|
||||
<Button size="sm" variant="outline" onClick={() => {/* 查看详情 */}}>
|
||||
<Eye className="w-3 h-3" />
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" onClick={() => handleEditApp(app)}>
|
||||
<Edit className="w-3 h-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,455 @@
|
||||
/**
|
||||
* filekorolheader: 应用运行对话框 - 模型应用运行和结果展示
|
||||
* 功能:应用参数输入、运行执行、结果可视化展示
|
||||
* 路径:/ai-crop-model/model-application/generation/components/ApplicationRunDialog
|
||||
* 规范:遵循crop-x/docs/开发项目规范.md,使用shadcn语义化样式
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { ApplicationGenerationState, ApplicationGenerationAction } from './ApplicationGenerationReducer';
|
||||
import { Application } from '@/types/ai-model';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import {
|
||||
PlayCircle,
|
||||
StopCircle,
|
||||
Loader2,
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
AlertTriangle,
|
||||
LineChart as LineChartIcon,
|
||||
PieChart as PieChartIcon,
|
||||
Table as TableIcon,
|
||||
FileText,
|
||||
} from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import {
|
||||
LineChart,
|
||||
Line,
|
||||
PieChart,
|
||||
Pie,
|
||||
Cell,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip as RechartsTooltip,
|
||||
Legend,
|
||||
ResponsiveContainer,
|
||||
} from 'recharts';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||
|
||||
interface ApplicationRunDialogProps {
|
||||
state: ApplicationGenerationState;
|
||||
dispatch: React.Dispatch<ApplicationGenerationAction>;
|
||||
}
|
||||
|
||||
export default function ApplicationRunDialog({ state, dispatch }: ApplicationRunDialogProps) {
|
||||
const { showRunDialog, runningApp, inputData, isRunning, runResult, dataSourceOptions, iotDeviceOptions } = state;
|
||||
const [selectedDataSources, setSelectedDataSources] = useState<Record<string, string>>({});
|
||||
|
||||
if (!runningApp) return null;
|
||||
|
||||
const handleInputChange = (field: string, value: string) => {
|
||||
dispatch({
|
||||
type: 'SET_INPUT_DATA',
|
||||
payload: { ...inputData, [field]: value }
|
||||
});
|
||||
};
|
||||
|
||||
const handleDataSourceChange = (field: string, dataSource: string) => {
|
||||
setSelectedDataSources(prev => ({ ...prev, [field]: dataSource }));
|
||||
};
|
||||
|
||||
const handleRunApplication = async () => {
|
||||
// 验证必填字段
|
||||
const missingFields = runningApp.inputConfig.fields.filter(field => !inputData[field]);
|
||||
if (missingFields.length > 0) {
|
||||
toast.error(`请填写以下字段: ${missingFields.join(', ')}`);
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({ type: 'SET_IS_RUNNING', payload: true });
|
||||
dispatch({ type: 'SET_RUN_RESULT', payload: null });
|
||||
|
||||
try {
|
||||
// 模拟应用运行过程
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
// 生成模拟结果数据
|
||||
const mockResult = generateMockResult(runningApp);
|
||||
dispatch({ type: 'SET_RUN_RESULT', payload: mockResult });
|
||||
toast.success('应用运行成功!');
|
||||
} catch (error) {
|
||||
toast.error('应用运行失败');
|
||||
} finally {
|
||||
dispatch({ type: 'SET_IS_RUNNING', payload: false });
|
||||
}
|
||||
};
|
||||
|
||||
const generateMockResult = (app: Application) => {
|
||||
switch (app.outputConfig.format) {
|
||||
case '折线图':
|
||||
return {
|
||||
type: 'line',
|
||||
data: [
|
||||
{ name: '1月', value: Math.random() * 100 },
|
||||
{ name: '2月', value: Math.random() * 100 },
|
||||
{ name: '3月', value: Math.random() * 100 },
|
||||
{ name: '4月', value: Math.random() * 100 },
|
||||
{ name: '5月', value: Math.random() * 100 },
|
||||
{ name: '6月', value: Math.random() * 100 },
|
||||
],
|
||||
analysis: `${app.name}分析完成,建议继续当前策略。`
|
||||
};
|
||||
case '饼状图':
|
||||
return {
|
||||
type: 'pie',
|
||||
data: [
|
||||
{ name: '正常', value: 65, color: '#10b981' },
|
||||
{ name: '注意', value: 25, color: '#f59e0b' },
|
||||
{ name: '警告', value: 10, color: '#ef4444' },
|
||||
],
|
||||
analysis: `${app.name}分析完成,65%区域状态正常,需要关注25%区域。`
|
||||
};
|
||||
case '表格':
|
||||
return {
|
||||
type: 'table',
|
||||
data: [
|
||||
{ 项目: '土壤湿度', 数值: '65%', 状态: '正常', 建议: '保持当前灌溉策略' },
|
||||
{ 项目: '氮含量', 数值: '120mg/kg', 状态: '偏低', 建议: '建议补充氮肥' },
|
||||
{ 项目: 'pH值', 数值: '6.8', 状态: '正常', 建议: '无需调节' },
|
||||
{ 项目: '温度', 数值: '25°C', 状态: '适宜', 建议: '适合作物生长' },
|
||||
],
|
||||
analysis: `${app.name}分析完成,各项指标基本正常,建议适当补充氮肥。`
|
||||
};
|
||||
case '文字':
|
||||
return {
|
||||
type: 'text',
|
||||
content: `
|
||||
## ${app.name}分析报告
|
||||
|
||||
### 总体评估
|
||||
根据当前输入数据分析,${app.name}系统运行状态良好。
|
||||
|
||||
### 详细分析
|
||||
1. **环境条件**: 当前温度25°C,湿度65%,pH值6.8,均处于适宜范围。
|
||||
2. **营养状况**: 氮含量120mg/kg,略低于推荐值,建议适当补充。
|
||||
3. **水分管理**: 土壤湿度65%,处于理想状态,可维持当前灌溉策略。
|
||||
|
||||
### 建议措施
|
||||
- 建议在未来一周内补充氮肥,用量为15kg/亩
|
||||
- 继续保持当前灌溉策略,每周监测土壤湿度变化
|
||||
- 密切关注天气变化,如遇降雨需调整灌溉计划
|
||||
|
||||
### 预期效果
|
||||
按照建议措施实施,预计作物产量可提升5-8%,品质得到改善。
|
||||
`,
|
||||
analysis: `${app.name}综合分析报告生成完成。`
|
||||
};
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
dispatch({ type: 'SET_SHOW_RUN_DIALOG', payload: false });
|
||||
dispatch({ type: 'SET_RUNNING_APP', payload: null });
|
||||
dispatch({ type: 'SET_INPUT_DATA', payload: {} });
|
||||
dispatch({ type: 'SET_RUN_RESULT', payload: null });
|
||||
setSelectedDataSources({});
|
||||
};
|
||||
|
||||
const renderOutputResult = () => {
|
||||
if (!runResult) return null;
|
||||
|
||||
switch (runResult.type) {
|
||||
case 'line':
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="h-64">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart data={runResult.data}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis />
|
||||
<RechartsTooltip />
|
||||
<Legend />
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="value"
|
||||
stroke="#3b82f6"
|
||||
strokeWidth={2}
|
||||
dot={{ fill: '#3b82f6' }}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
<Card className="p-4 bg-blue-50 dark:bg-blue-950 border-blue-200 dark:border-blue-800">
|
||||
<p className="text-sm text-blue-900 dark:text-blue-100">{runResult.analysis}</p>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'pie':
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="h-64">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<PieChart>
|
||||
<Pie
|
||||
data={runResult.data}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
labelLine={false}
|
||||
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
|
||||
outerRadius={80}
|
||||
fill="#8884d8"
|
||||
dataKey="value"
|
||||
>
|
||||
{runResult.data.map((entry: any, index: number) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
<RechartsTooltip />
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
<Card className="p-4 bg-green-50 dark:bg-green-950 border-green-200 dark:border-green-800">
|
||||
<p className="text-sm text-green-900 dark:text-green-100">{runResult.analysis}</p>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'table':
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>项目</TableHead>
|
||||
<TableHead>数值</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>建议</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{runResult.data.map((row: any, index: number) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell className="font-medium">{row.项目}</TableCell>
|
||||
<TableCell>{row.数值}</TableCell>
|
||||
<TableCell>
|
||||
<span className={`px-2 py-1 rounded text-xs ${
|
||||
row.状态 === '正常'
|
||||
? 'bg-green-100 text-green-800'
|
||||
: row.状态 === '偏低'
|
||||
? 'bg-yellow-100 text-yellow-800'
|
||||
: 'bg-gray-100 text-gray-800'
|
||||
}`}>
|
||||
{row.状态}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-sm">{row.建议}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Card className="p-4 bg-purple-50 dark:bg-purple-950 border-purple-200 dark:border-purple-800">
|
||||
<p className="text-sm text-purple-900 dark:text-purple-100">{runResult.analysis}</p>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'text':
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Card className="p-6">
|
||||
<div className="prose prose-sm max-w-none dark:prose-invert">
|
||||
<pre className="whitespace-pre-wrap text-sm font-sans">
|
||||
{runResult.content}
|
||||
</pre>
|
||||
</div>
|
||||
</Card>
|
||||
<Card className="p-4 bg-orange-50 dark:bg-orange-950 border-orange-200 dark:border-orange-800">
|
||||
<p className="text-sm text-orange-900 dark:text-orange-100">{runResult.analysis}</p>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={showRunDialog} onOpenChange={handleClose}>
|
||||
<DialogContent className="max-w-5xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<PlayCircle className="w-5 h-5 text-blue-600" />
|
||||
运行应用:{runningApp.name}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
填写输入参数并运行应用,查看分析结果
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* 应用信息 */}
|
||||
<Card className="p-4 bg-gradient-to-r from-blue-50 to-cyan-50 border-blue-200">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div>
|
||||
<Label className="text-xs text-muted-foreground">应用类型</Label>
|
||||
<div className="font-medium">{runningApp.type}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-xs text-muted-foreground">模型</Label>
|
||||
<div className="font-medium">{runningApp.modelName}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-xs text-muted-foreground">版本</Label>
|
||||
<div className="font-mono text-xs">{runningApp.modelVersion}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-xs text-muted-foreground">输出格式</Label>
|
||||
<div className="font-medium">{runningApp.outputConfig.format}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* 输入参数 */}
|
||||
{!runResult && (
|
||||
<Card className="p-4">
|
||||
<h3 className="font-semibold mb-4">输入参数</h3>
|
||||
<div className="space-y-4">
|
||||
{runningApp.inputConfig.fields.map((field) => (
|
||||
<div key={field} className="space-y-2">
|
||||
<Label>{field} *</Label>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex-1">
|
||||
{field.includes('图像') || field.includes('图片') ? (
|
||||
<Textarea
|
||||
placeholder={`请输入${field}的描述或上传图片链接`}
|
||||
value={inputData[field] || ''}
|
||||
onChange={(e) => handleInputChange(field, e.target.value)}
|
||||
rows={3}
|
||||
/>
|
||||
) : field.includes('选择') || field.includes('类型') ? (
|
||||
<Select
|
||||
value={inputData[field] || ''}
|
||||
onValueChange={(value) => handleInputChange(field, value)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={`请选择${field}`} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="选项1">选项1</SelectItem>
|
||||
<SelectItem value="选项2">选项2</SelectItem>
|
||||
<SelectItem value="选项3">选项3</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
) : field.includes('时间') || field.includes('日期') ? (
|
||||
<Input
|
||||
type="datetime-local"
|
||||
value={inputData[field] || ''}
|
||||
onChange={(e) => handleInputChange(field, e.target.value)}
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
placeholder={`请输入${field}`}
|
||||
value={inputData[field] || ''}
|
||||
onChange={(e) => handleInputChange(field, e.target.value)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="w-48">
|
||||
<Select
|
||||
value={selectedDataSources[field] || ''}
|
||||
onValueChange={(value) => handleDataSourceChange(field, value)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="数据来源" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="手动输入">手动输入</SelectItem>
|
||||
{dataSourceOptions.map((source) => (
|
||||
<SelectItem key={source} value={source}>
|
||||
{source}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
{selectedDataSources[field] && selectedDataSources[field] !== '手动输入' && (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
数据来源: {selectedDataSources[field]}
|
||||
{selectedDataSources[field].includes('传感器') && (
|
||||
<Select defaultValue="">
|
||||
<SelectTrigger className="w-full mt-1">
|
||||
<SelectValue placeholder="选择设备" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{iotDeviceOptions.map((device) => (
|
||||
<SelectItem key={device} value={device}>
|
||||
{device}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 运行结果 */}
|
||||
{runResult && (
|
||||
<Card className="p-4">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<CheckCircle className="w-5 h-5 text-green-600" />
|
||||
<h3 className="font-semibold">运行结果</h3>
|
||||
</div>
|
||||
{renderOutputResult()}
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<div className="flex justify-between w-full">
|
||||
<Button variant="outline" onClick={handleClose}>
|
||||
{runResult ? '关闭' : '取消'}
|
||||
</Button>
|
||||
{!runResult && (
|
||||
<Button
|
||||
onClick={handleRunApplication}
|
||||
disabled={isRunning}
|
||||
className="bg-blue-600 hover:bg-blue-700"
|
||||
>
|
||||
{isRunning ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
运行中...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<PlayCircle className="w-4 h-4 mr-2" />
|
||||
运行应用
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -1,18 +1,303 @@
|
||||
/**
|
||||
* filekorolheader: 模型应用中心生成页面 - AI模型应用生成管理
|
||||
* 功能:应用生成、应用管理、应用运行、结果展示
|
||||
* 路径:/ai-crop-model/model-application/generation
|
||||
* 规范:遵循crop-x/docs/开发项目规范.md,使用useReducer状态管理,shadcn语义化样式
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import { useReducer } from 'react';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import {
|
||||
Rocket,
|
||||
Plus,
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
AlertCircle,
|
||||
PlayCircle,
|
||||
PauseCircle,
|
||||
StopCircle,
|
||||
Eye,
|
||||
Edit,
|
||||
Clock,
|
||||
Zap,
|
||||
BarChart3,
|
||||
Activity,
|
||||
Droplets,
|
||||
Bug,
|
||||
Sprout,
|
||||
Terminal,
|
||||
FileText,
|
||||
Timer,
|
||||
TrendingUp,
|
||||
Server,
|
||||
Cpu,
|
||||
AlertTriangle,
|
||||
CheckCircle2,
|
||||
ChevronRight,
|
||||
ChevronLeft,
|
||||
Sparkles,
|
||||
Table as TableIcon,
|
||||
Type,
|
||||
PieChart as PieChartIcon,
|
||||
LineChart as LineChartIcon,
|
||||
} from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import {
|
||||
LineChart,
|
||||
Line,
|
||||
PieChart,
|
||||
Pie,
|
||||
Cell,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip as RechartsTooltip,
|
||||
Legend,
|
||||
ResponsiveContainer,
|
||||
} from 'recharts';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||
import { ApplicationGenerationReducer, ApplicationGenerationState, ApplicationGenerationAction } from './components/ApplicationGenerationReducer';
|
||||
import ApplicationGenerateDialog from './components/ApplicationGenerateDialog';
|
||||
import ApplicationRunDialog from './components/ApplicationRunDialog';
|
||||
import ApplicationList from './components/ApplicationList';
|
||||
import ApplicationEditDialog from './components/ApplicationEditDialog';
|
||||
|
||||
export default function GenerationPage() {
|
||||
const [state, dispatch] = useReducer(ApplicationGenerationReducer, {
|
||||
applications: [
|
||||
{
|
||||
id: 'app-1',
|
||||
name: '智能灌溉策略生成',
|
||||
type: '智能灌溉',
|
||||
description: '基于土壤湿度、天气预报和作物需水模型,自动生成最优灌溉方案',
|
||||
modelName: '灌溉优化模型',
|
||||
modelVersion: 'v2.1.0',
|
||||
inputConfig: {
|
||||
fields: ['土壤湿度', '温度', '降水预报', '作物类型', '生长阶段'],
|
||||
},
|
||||
outputConfig: {
|
||||
format: '折线图',
|
||||
},
|
||||
status: '运行中',
|
||||
createTime: '2024-01-15',
|
||||
lastRunTime: '2024-03-20 14:30',
|
||||
runCount: 156,
|
||||
successRate: 98.5,
|
||||
avgExecutionTime: 2.3,
|
||||
},
|
||||
{
|
||||
id: 'app-2',
|
||||
name: '病虫害智能预警',
|
||||
type: '病虫害预警',
|
||||
description: '通过图像识别和环境数据分析,提前预警病虫害风险',
|
||||
modelName: '病虫害识别模型',
|
||||
modelVersion: 'v1.8.0',
|
||||
inputConfig: {
|
||||
fields: ['作物图像', '温度', '湿度', '叶片状况', '历史发病记录'],
|
||||
},
|
||||
outputConfig: {
|
||||
format: '饼状图',
|
||||
},
|
||||
status: '运行中',
|
||||
createTime: '2024-01-20',
|
||||
lastRunTime: '2024-03-20 16:45',
|
||||
runCount: 89,
|
||||
successRate: 96.2,
|
||||
avgExecutionTime: 1.8,
|
||||
},
|
||||
{
|
||||
id: 'app-3',
|
||||
name: '精准施肥推荐',
|
||||
type: '施肥推荐',
|
||||
description: '根据土壤养分和作物需求,生成精准施肥方案',
|
||||
modelName: '营养需求模型',
|
||||
modelVersion: 'v1.5.0',
|
||||
inputConfig: {
|
||||
fields: ['土壤养分', '作物类型', '生长阶段', '目标产量', '历史施肥记录'],
|
||||
},
|
||||
outputConfig: {
|
||||
format: '表格',
|
||||
},
|
||||
status: '已停止',
|
||||
createTime: '2024-02-01',
|
||||
lastRunTime: '2024-03-18 09:15',
|
||||
runCount: 45,
|
||||
successRate: 94.7,
|
||||
avgExecutionTime: 3.1,
|
||||
},
|
||||
],
|
||||
showGenerateDialog: false,
|
||||
generateStep: 1,
|
||||
newAppData: {
|
||||
name: '',
|
||||
type: '',
|
||||
description: '',
|
||||
modelName: '',
|
||||
modelVersion: '',
|
||||
inputFields: [],
|
||||
outputFormat: '',
|
||||
},
|
||||
showRunDialog: false,
|
||||
runningApp: null,
|
||||
inputData: {},
|
||||
isRunning: false,
|
||||
runResult: null,
|
||||
showEditDialog: false,
|
||||
editingApp: null,
|
||||
editStep: 1,
|
||||
editAppData: {
|
||||
name: '',
|
||||
type: '',
|
||||
description: '',
|
||||
modelName: '',
|
||||
modelVersion: '',
|
||||
inputFields: [],
|
||||
outputFormat: '',
|
||||
},
|
||||
availableModels: [
|
||||
{ id: 'm1', name: '灌溉优化模型', version: 'v2.1.0', type: 'TensorFlow', status: '运行中' },
|
||||
{ id: 'm2', name: '病虫害识别模型', version: 'v1.8.0', type: 'PyTorch', status: '运行中' },
|
||||
{ id: 'm3', name: '营养需求模型', version: 'v1.5.0', type: 'TensorFlow', status: '运行中' },
|
||||
{ id: 'm4', name: '产量预测模型', version: 'v2.0.0', type: 'XGBoost', status: '运行中' },
|
||||
{ id: 'm5', name: '生长监测模型', version: 'v1.3.0', type: 'PyTorch', status: '运行中' },
|
||||
{ id: 'm6', name: '天气预测模型', version: 'v1.6.0', type: 'LSTM', status: '运行中' },
|
||||
],
|
||||
inputFieldOptions: [
|
||||
'土壤湿度', '温度', '湿度', '光照强度', 'pH值', '电导率', '氮含量', '磷含量', '钾含量',
|
||||
'降水预报', '风速', '风向', '大气压力', '作物图像', '叶片状况', '株高', '叶面积指数',
|
||||
'作物类型', '生长阶段', '种植密度', '目标产量', '历史发病记录', '历史施肥记录', '土壤类型',
|
||||
'地理位置', '海拔高度', '坡度', '坡向', '土壤容重', '有机质含量', '微量元素含量',
|
||||
'病虫害图像', '虫害密度', '病情指数', '天敌数量', '农药使用记录', '施肥记录', '灌溉记录',
|
||||
'农事操作记录', '气象数据', '卫星遥感数据', '无人机影像数据', '传感器实时数据',
|
||||
],
|
||||
dataSourceOptions: [
|
||||
'传感器实时数据',
|
||||
'数据库查询',
|
||||
'手动输入',
|
||||
'第三方API',
|
||||
'历史数据',
|
||||
'配置文件',
|
||||
'IoT设备',
|
||||
'气象站',
|
||||
],
|
||||
iotDeviceOptions: [
|
||||
'土壤传感器01 (SN:SS-001)',
|
||||
'温湿度传感器02 (SN:TH-002)',
|
||||
'光照传感器03 (SN:LS-003)',
|
||||
'pH传感器04 (SN:PH-004)',
|
||||
'水肥一体机05 (SN:WF-005)',
|
||||
'气象站06 (SN:WS-006)',
|
||||
'作物图像采集器07 (SN:IC-007)',
|
||||
'水泵控制器08 (SN:PC-008)',
|
||||
],
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Card className="p-6">
|
||||
<h2 className="text-xl font-semibold">生成调度</h2>
|
||||
<div className="p-3 bg-muted rounded-lg mt-3">
|
||||
<p className="text-sm">
|
||||
<strong>页面路径:</strong> /ai-crop-model/model-application/generation
|
||||
</p>
|
||||
{/* 页面标题和描述 */}
|
||||
<Card className="p-6 bg-gradient-to-r from-blue-50 to-cyan-50 border-blue-200">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<Rocket className="w-8 h-8 text-blue-600" />
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-blue-900">应用生成</h1>
|
||||
<p className="text-blue-700">AI模型应用生成与管理平台</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">应用总数</p>
|
||||
<p className="mt-2 text-3xl text-blue-600">{state.applications.length}</p>
|
||||
<p className="text-xs text-blue-600 mt-1">已创建应用</p>
|
||||
</div>
|
||||
<Rocket className="w-12 h-12 text-blue-600 opacity-50" />
|
||||
</div>
|
||||
</Card>
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">运行中</p>
|
||||
<p className="mt-2 text-3xl text-green-600">
|
||||
{state.applications.filter(app => app.status === '运行中').length}
|
||||
</p>
|
||||
<p className="text-xs text-green-600 mt-1">正常运行</p>
|
||||
</div>
|
||||
<PlayCircle className="w-12 h-12 text-green-600 opacity-50" />
|
||||
</div>
|
||||
</Card>
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">总执行次数</p>
|
||||
<p className="mt-2 text-3xl text-purple-600">
|
||||
{state.applications.reduce((sum, app) => sum + app.runCount, 0)}
|
||||
</p>
|
||||
<p className="text-xs text-purple-600 mt-1">累计运行</p>
|
||||
</div>
|
||||
<Zap className="w-12 h-12 text-purple-600 opacity-50" />
|
||||
</div>
|
||||
</Card>
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">成功率</p>
|
||||
<p className="mt-2 text-3xl text-orange-600">
|
||||
{(state.applications.reduce((sum, app) => sum + app.successRate, 0) / state.applications.length).toFixed(1)}%
|
||||
</p>
|
||||
<p className="text-xs text-orange-600 mt-1">平均成功率</p>
|
||||
</div>
|
||||
<CheckCircle2 className="w-12 h-12 text-orange-600 opacity-50" />
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* 应用生成功能说明 */}
|
||||
<Card className="p-4 bg-gradient-to-r from-blue-50 to-cyan-50 dark:from-blue-950 dark:to-cyan-950 border-blue-200 dark:border-blue-800">
|
||||
<div className="flex items-start gap-2">
|
||||
<Rocket className="w-5 h-5 text-blue-600 flex-shrink-0 mt-0.5" />
|
||||
<div className="text-sm text-blue-900">
|
||||
<p className="mb-2">应用生成功能:</p>
|
||||
<ul className="space-y-1 text-xs">
|
||||
<li>• <strong>可视化配置</strong>: 通过配置化方式快速生成业务应用</li>
|
||||
<li>• <strong>灵活定制</strong>: 自定义输入字段与输出格式</li>
|
||||
<li>• <strong>多种输出</strong>: 支持折线图、饼状图、表格、文字等多种输出格式</li>
|
||||
<li>• <strong>场景融合</strong>: 实现AI模型与农业生产场景深度融合</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<Button
|
||||
onClick={() => dispatch({ type: 'SET_SHOW_GENERATE_DIALOG', payload: true })}
|
||||
className="bg-blue-600 hover:bg-blue-700"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
创建应用
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* 应用列表 */}
|
||||
<ApplicationList state={state} dispatch={dispatch} />
|
||||
|
||||
{/* 应用生成对话框 */}
|
||||
<ApplicationGenerateDialog state={state} dispatch={dispatch} />
|
||||
|
||||
{/* 应用运行对话框 */}
|
||||
<ApplicationRunDialog state={state} dispatch={dispatch} />
|
||||
|
||||
{/* 应用编辑对话框 */}
|
||||
<ApplicationEditDialog state={state} dispatch={dispatch} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
76
crop-x/src/types/ai-model.ts
Normal file
76
crop-x/src/types/ai-model.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* filekorolheader: AI模型类型定义 - 模型应用中心相关类型
|
||||
* 功能:应用类型、执行历史、调度任务等类型定义
|
||||
* 路径:/types/ai-model.ts
|
||||
* 规范:遵循crop-x/docs/开发项目规范.md,使用TypeScript类型安全
|
||||
*/
|
||||
|
||||
export type ApplicationType = '智能灌溉' | '病虫害预警' | '施肥推荐' | '产量预测' | '生长监测' | '其他';
|
||||
export type ApplicationStatus = '运行中' | '已停止' | '故障';
|
||||
export type TriggerType = '定时触发' | '周期触发' | '事件触发' | '手动触发';
|
||||
export type TaskStatus = '等待中' | '运行中' | '已完成' | '失败' | '已取消';
|
||||
export type Priority = '高' | '中' | '低';
|
||||
export type OutputFormat = '折线图' | '饼状图' | '表格' | '文字';
|
||||
|
||||
export interface Application {
|
||||
id: string;
|
||||
name: string;
|
||||
type: ApplicationType;
|
||||
description: string;
|
||||
modelName: string;
|
||||
modelVersion: string;
|
||||
inputConfig: {
|
||||
fields: string[];
|
||||
};
|
||||
outputConfig: {
|
||||
format: OutputFormat;
|
||||
};
|
||||
status: ApplicationStatus;
|
||||
createTime: string;
|
||||
lastRunTime?: string;
|
||||
runCount: number;
|
||||
successRate: number;
|
||||
avgExecutionTime: number;
|
||||
}
|
||||
|
||||
export interface ExecutionHistory {
|
||||
id: string;
|
||||
executeTime: string;
|
||||
status: TaskStatus;
|
||||
executionTime: number;
|
||||
result?: string;
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
export interface ScheduleTask {
|
||||
id: string;
|
||||
taskNo: string;
|
||||
applicationName: string;
|
||||
triggerType: TriggerType;
|
||||
priority: Priority;
|
||||
scheduledTime: string;
|
||||
actualStartTime?: string;
|
||||
actualEndTime?: string;
|
||||
status: TaskStatus;
|
||||
executionTime?: number;
|
||||
retryCount: number;
|
||||
maxRetry: number;
|
||||
errorMessage?: string;
|
||||
result?: string;
|
||||
executionHistory?: ExecutionHistory[];
|
||||
}
|
||||
|
||||
export interface TaskQueue {
|
||||
waiting: number;
|
||||
running: number;
|
||||
completed: number;
|
||||
failed: number;
|
||||
}
|
||||
|
||||
export interface ModelService {
|
||||
id: string;
|
||||
name: string;
|
||||
version: string;
|
||||
type: string;
|
||||
status: string;
|
||||
}
|
||||
Reference in New Issue
Block a user