"use client"; import { useMemo, useState } from "react"; import Alert from "@mui/material/Alert"; import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; import Card from "@mui/material/Card"; import CardContent from "@mui/material/CardContent"; import Chip from "@mui/material/Chip"; import Divider from "@mui/material/Divider"; import Grid from "@mui/material/Grid2"; import LinearProgress from "@mui/material/LinearProgress"; import Stack from "@mui/material/Stack"; import Step from "@mui/material/Step"; import StepLabel from "@mui/material/StepLabel"; import Stepper from "@mui/material/Stepper"; import Tab from "@mui/material/Tab"; import Tabs from "@mui/material/Tabs"; import Typography from "@mui/material/Typography"; import { alpha, useTheme } from "@mui/material/styles"; import type { ThemeColor } from "@core/types"; import HorizontalWithAvatar from "@/components/card-statistics/HorizontalWithAvatar"; import { useFarmHub } from "@/hooks/useFarmHub"; import { fertilizationPlanParserService, type FertilizationPlanAnswerValue, type FertilizationPlanApplication, type FertilizationPlanData, type FertilizationPlanParserResult, } from "@/libs/api/services/fertilizationPlanParserService"; import { irrigationPlanParserService, type IrrigationPlanAnswerValue, type IrrigationPlanData, type IrrigationPlanParserResult, } from "@/libs/api/services/irrigationPlanParserService"; import OptionMenu from "@core/components/option-menu"; import CustomAvatar from "@core/components/mui/Avatar"; import CustomTextField from "@core/components/mui/TextField"; type ParserTabKey = "fertilization" | "irrigation"; type ParserAnswerValue = | FertilizationPlanAnswerValue | IrrigationPlanAnswerValue; type ParserQuestion = { id: string; field: string; question: string; rationale: string; }; type ParserResponse = | FertilizationPlanParserResult | IrrigationPlanParserResult; type TabState = { message: string; response: ParserResponse | null; answers: Record; activeQuestion: number; requestError: string | null; statusNote: string | null; loading: boolean; }; type FieldMeta = { key: string; label: string; value: string | null; }; type ParserConfig = { key: ParserTabKey; label: string; badge: string; icon: string; heroTitle: string; heroDescription: string; panelTitle: string; panelDescription: string; inputLabel: string; inputPlaceholder: string; samplePrompts: string[]; fieldLabels: Record; previewOrder: string[]; itemCountTitle: string; primaryCardTitle: string; primaryFallbackTitle: string; finalCardTitle: string; finalCardDescription: string; buildPayload: (payload: { message?: string; answers?: Record; partialPlan?: unknown; farmUuid?: string; }) => Record; submit: (payload: Record) => Promise; getFieldValue: (plan: unknown, field: string) => ParserAnswerValue; formatFieldValue: (field: string, value: ParserAnswerValue) => string; getPrimaryItem: (plan: unknown) => unknown | null; getPrimaryMeta: (item: unknown) => FieldMeta[]; getPrimaryHeadline: (item: unknown) => string; getItemCount: (plan: unknown) => number; }; type FertilizationPlanParserPageProps = { initialTab?: ParserTabKey; enabledTabs?: ParserTabKey[]; }; const createInitialTabState = (): TabState => ({ message: "", response: null, answers: {}, activeQuestion: 0, requestError: null, statusNote: null, loading: false, }); const formatNumber = (value: number) => new Intl.NumberFormat("fa-IR").format(value); const defaultFormatFieldValue = (value: ParserAnswerValue) => { if (value === null || value === undefined || value === "") { return "—"; } if (typeof value === "number") { return formatNumber(value); } if (typeof value === "boolean") { return value ? "بله" : "خیر"; } return value; }; const formatNumberWithUnit = ( value: number | null | undefined, unit: string, ) => { if (value === null || value === undefined) { return null; } return `${formatNumber(value)} ${unit}`; }; const getStatusMeta = (status?: ParserResponse["status"]) => { if (status === "completed") { return { label: "آماده اجرا", color: "success" as const, icon: "tabler-rosette-discount-check", }; } if (status === "needs_clarification") { return { label: "نیازمند تکمیل", color: "warning" as const, icon: "tabler-help-hexagon", }; } return { label: "در انتظار تحلیل", color: "secondary" as const, icon: "tabler-sparkles", }; }; const extractErrorMessage = (error: unknown, fallback: string) => { if (typeof error !== "object" || error === null) { return fallback; } if ( "details" in error && error.details && typeof error.details === "object" && "data" in error.details && error.details.data && typeof error.details.data === "object" ) { const data = error.details.data as Record; const nonFieldErrors = data.non_field_errors; if ( Array.isArray(nonFieldErrors) && typeof nonFieldErrors[0] === "string" ) { return nonFieldErrors[0]; } } if ("message" in error && typeof error.message === "string") { return error.message; } return fallback; }; const createJsonSnapshot = (plan: unknown) => { if (!plan) { return "{}"; } return JSON.stringify(plan, null, 2); }; const getFertilizationFieldValue = (plan: unknown, field: string) => { const data = plan as FertilizationPlanData | null | undefined; if (!data) return null; if (field === "crop_name") return data.crop_name; if (field === "growth_stage") return data.growth_stage; if (field === "objective") return data.objective; const application = data.applications?.[0]; if (!application) return null; if (field === "fertilizer_name") return application.fertilizer_name; if (field === "formula") return application.formula; if (field === "amount") return application.amount; if (field === "application_method") return application.application_method; if (field === "timing") return application.timing; if (field === "interval_days") return application.interval_days; if (field === "purpose") return application.purpose; return null; }; const getIrrigationFieldValue = (plan: unknown, field: string) => { const data = plan as IrrigationPlanData | null | undefined; if (!data) return null; if (field === "crop_name") return data.crop_name; if (field === "growth_stage") return data.growth_stage; if (field === "irrigation_method") return data.irrigation_method; if (field === "water_amount_per_event") return data.water_amount_per_event; if (field === "duration_minutes") return data.duration_minutes; if (field === "frequency_text") return data.frequency_text; if (field === "interval_days") return data.interval_days; if (field === "preferred_time_of_day") return data.preferred_time_of_day; if (field === "start_date") return data.start_date; if (field === "target_area") return data.target_area; return null; }; const buildNextAnswersState = ( response: ParserResponse, previousAnswers: Record, config: ParserConfig, ) => { return response.questions.reduce>((acc, question) => { const previousValue = previousAnswers[question.field]; if (previousValue) { acc[question.field] = previousValue; return acc; } const extractedValue = config.getFieldValue( response.collected_data, question.field, ); if (extractedValue !== null && extractedValue !== undefined) { acc[question.field] = String(extractedValue); } return acc; }, {}); }; const normalizeAnswers = ( questions: ParserQuestion[], answers: Record, ) => { return questions.reduce>( (acc, question) => { const rawValue = answers[question.field]?.trim(); if (!rawValue) { return acc; } if ( question.field === "interval_days" || question.field === "duration_minutes" ) { const parsed = Number(rawValue); acc[question.field] = Number.isFinite(parsed) ? parsed : rawValue; return acc; } acc[question.field] = rawValue; return acc; }, {}, ); }; const FERTILIZATION_FIELD_LABELS: Record = { crop_name: "محصول", growth_stage: "مرحله رشد", objective: "هدف برنامه", fertilizer_name: "نام کود", formula: "فرمول کود", amount: "مقدار مصرف", application_method: "روش مصرف", timing: "زمان بندی", interval_days: "فاصله نوبت ها", purpose: "هدف هر نوبت", }; const IRRIGATION_FIELD_LABELS: Record = { crop_name: "محصول", growth_stage: "مرحله رشد", irrigation_method: "روش آبیاری", water_amount_per_event: "مقدار آب هر نوبت", duration_minutes: "مدت هر نوبت", frequency_text: "تناوب آبیاری", interval_days: "فاصله آبیاری", preferred_time_of_day: "زمان مناسب اجرا", start_date: "زمان شروع", target_area: "محدوده اجرا", }; const PARSER_CONFIGS: Record = { fertilization: { key: "fertilization", label: "کودهی", badge: "Fertilization AI Planner", icon: "tabler-flask-2", heroTitle: "برنامه کودهی با ورودی متنی و خروجی JSON آماده اجرا", heroDescription: "متن آزاد کشاورز را بگیر، اگر داده ناقص بود سوال تکمیلی بپرس و در نهایت یک نسخه ساختاریافته و خوش خوان از برنامه کودهی تحویل بده.", panelTitle: "اتاق فرمان برنامه کودهی", panelDescription: "هر چقدر متن طبیعی تر و نزدیک به زبان خود کشاورز باشد، API بهتر می تواند ساختار نهایی را استخراج کند.", inputLabel: "متن آزاد برنامه کودهی", inputPlaceholder: "مثلا: برای گندم در مرحله پنجه زنی هر 12 روز یک بار کود 20-20-20 به مقدار 35 کیلوگرم در هکتار از طریق کودآبیاری می دهم.", samplePrompts: [ "برای گندم در مرحله پنجه زنی هر 12 روز یک بار کود 20-20-20 به مقدار 35 کیلوگرم در هکتار از طریق کودآبیاری می دهم.", "برای ذرت از کود کامل استفاده می کنم اما فاصله بین نوبت ها را دقیق نمی دانم و می خواهم برنامه ام کامل شود.", "برای گوجه فرنگی در شروع گلدهی یک برنامه کودهی دقیق با کود NPK و مصرف مرحله ای می خواهم.", ], fieldLabels: FERTILIZATION_FIELD_LABELS, previewOrder: [ "crop_name", "growth_stage", "objective", "fertilizer_name", "formula", "amount", "application_method", "timing", "interval_days", "purpose", ], itemCountTitle: "نوبت های کودی", primaryCardTitle: "نسخه پیشنهادی برای اولین نوبت کودی", primaryFallbackTitle: "نام کود هنوز مشخص نیست", finalCardTitle: "خروجی نهایی کودهی آماده تحویل", finalCardDescription: "اگر لازم بود همین JSON را برای ذخیره، اشتراک گذاری یا مرحله بعدی workflow استفاده کن.", buildPayload: ({ message, answers, partialPlan, farmUuid }) => ({ ...(message ? { message } : {}), ...(answers ? { answers } : {}), ...(partialPlan ? { partial_plan: partialPlan } : {}), ...(farmUuid ? { farm_uuid: farmUuid } : {}), }), submit: (payload) => fertilizationPlanParserService.parseFromText( payload as Parameters< typeof fertilizationPlanParserService.parseFromText >[0], ), getFieldValue: getFertilizationFieldValue, formatFieldValue: (field, value) => { if (field === "interval_days" && typeof value === "number") { return `${formatNumber(value)} روز`; } return defaultFormatFieldValue(value); }, getPrimaryItem: (plan) => (plan as FertilizationPlanData | null | undefined)?.applications?.[0] ?? null, getPrimaryMeta: (item) => { const application = item as FertilizationPlanApplication | null; if (!application) return []; return [ { key: "formula", label: FERTILIZATION_FIELD_LABELS.formula, value: application.formula, }, { key: "amount", label: FERTILIZATION_FIELD_LABELS.amount, value: application.amount, }, { key: "application_method", label: FERTILIZATION_FIELD_LABELS.application_method, value: application.application_method, }, { key: "timing", label: FERTILIZATION_FIELD_LABELS.timing, value: application.timing, }, { key: "interval_days", label: FERTILIZATION_FIELD_LABELS.interval_days, value: formatNumberWithUnit(application.interval_days, "روز"), }, { key: "purpose", label: FERTILIZATION_FIELD_LABELS.purpose, value: application.purpose, }, ]; }, getPrimaryHeadline: (item) => (item as FertilizationPlanApplication | null)?.fertilizer_name || "نام کود هنوز مشخص نیست", getItemCount: (plan) => (plan as FertilizationPlanData | null | undefined)?.applications ?.length ?? 0, }, irrigation: { key: "irrigation", label: "آبیاری", badge: "Irrigation AI Planner", icon: "tabler-droplet-half-2", heroTitle: "برنامه آبیاری با ورودی متنی و خروجی JSON آماده اجرا", heroDescription: "برای آبیاری هم همان flow هوشمند را داشته باش: متن آزاد را بفرست، ابهام ها را کامل کن و یک برنامه ساختاریافته و قابل اجرا بگیر.", panelTitle: "اتاق فرمان برنامه آبیاری", panelDescription: "جزئیات روش آبیاری، زمان بندی و مقدار آب را با زبان طبیعی بنویس تا API آن را به برنامه دقیق تبدیل کند.", inputLabel: "متن آزاد برنامه آبیاری", inputPlaceholder: "مثلا: برای گوجه فرنگی با آبیاری قطره ای هر سه روز یک بار صبح زود 25 دقیقه آبیاری می کنم و حدود 18 لیتر برای هر بوته می دهم.", samplePrompts: [ "برای گوجه فرنگی با آبیاری قطره ای هر سه روز یک بار صبح زود 25 دقیقه آبیاری می کنم و حدود 18 لیتر برای هر بوته می دهم.", "برای هندوانه هر دو روز یک بار آبیاری می کنم اما زمان شروع برنامه و محدوده اجرا را هنوز مشخص نکرده ام.", "برای خیار گلخانه ای با تیپ آبیاری می کنم و می خواهم برنامه ام بر اساس زمان و حجم آب کامل شود.", ], fieldLabels: IRRIGATION_FIELD_LABELS, previewOrder: [ "crop_name", "growth_stage", "irrigation_method", "water_amount_per_event", "duration_minutes", "frequency_text", "interval_days", "preferred_time_of_day", "start_date", "target_area", ], itemCountTitle: "بخش های آبیاری", primaryCardTitle: "چکیده اجرای برنامه آبیاری", primaryFallbackTitle: "روش آبیاری هنوز مشخص نیست", finalCardTitle: "خروجی نهایی آبیاری آماده تحویل", finalCardDescription: "از همین JSON می توانی برای نمایش، ذخیره یا ادامه workflow آبیاری استفاده کنی.", buildPayload: ({ message, answers, partialPlan, farmUuid }) => ({ ...(message ? { message } : {}), ...(answers ? { answers } : {}), ...(partialPlan ? { partial_plan: partialPlan } : {}), ...(farmUuid ? { farm_uuid: farmUuid } : {}), }), submit: (payload) => irrigationPlanParserService.parseFromText( payload as Parameters< typeof irrigationPlanParserService.parseFromText >[0], ), getFieldValue: getIrrigationFieldValue, formatFieldValue: (field, value) => { if (field === "duration_minutes" && typeof value === "number") { return `${formatNumber(value)} دقیقه`; } if (field === "interval_days" && typeof value === "number") { return `${formatNumber(value)} روز`; } return defaultFormatFieldValue(value); }, getPrimaryItem: (plan) => plan ?? null, getPrimaryMeta: (item) => { const data = item as IrrigationPlanData | null; if (!data) return []; return [ { key: "irrigation_method", label: IRRIGATION_FIELD_LABELS.irrigation_method, value: data.irrigation_method, }, { key: "water_amount_per_event", label: IRRIGATION_FIELD_LABELS.water_amount_per_event, value: data.water_amount_per_event, }, { key: "duration_minutes", label: IRRIGATION_FIELD_LABELS.duration_minutes, value: formatNumberWithUnit(data.duration_minutes, "دقیقه"), }, { key: "frequency_text", label: IRRIGATION_FIELD_LABELS.frequency_text, value: data.frequency_text, }, { key: "preferred_time_of_day", label: IRRIGATION_FIELD_LABELS.preferred_time_of_day, value: data.preferred_time_of_day, }, { key: "target_area", label: IRRIGATION_FIELD_LABELS.target_area, value: data.target_area, }, ]; }, getPrimaryHeadline: (item) => (item as IrrigationPlanData | null)?.irrigation_method || "روش آبیاری هنوز مشخص نیست", getItemCount: (plan) => { const data = plan as IrrigationPlanData | null | undefined; if (!data) return 0; return 1 + (data.trigger_conditions?.length ?? 0); }, }, }; const FertilizationPlanParserPage = ({ initialTab = "fertilization", enabledTabs = ["fertilization", "irrigation"], }: FertilizationPlanParserPageProps) => { const theme = useTheme(); const { farmHub } = useFarmHub(); const farmUuid = farmHub?.farm_uuid; const availableTabs = enabledTabs.filter( (tab): tab is ParserTabKey => tab in PARSER_CONFIGS, ); const resolvedInitialTab = availableTabs.includes(initialTab) ? initialTab : availableTabs[0] ?? "fertilization"; const singleTabMode = availableTabs.length === 1; const [activeTab, setActiveTab] = useState(resolvedInitialTab); const [tabStates, setTabStates] = useState>({ fertilization: createInitialTabState(), irrigation: createInitialTabState(), }); const config = PARSER_CONFIGS[activeTab]; const currentState = tabStates[activeTab]; const statusMeta = getStatusMeta(currentState.response?.status); const planPreview = currentState.response?.final_plan ?? currentState.response?.collected_data ?? null; const primaryItem = config.getPrimaryItem(planPreview); const currentQuestion = currentState.response?.questions[currentState.activeQuestion] ?? null; const completionValue = currentState.response ? Math.max( 20, Math.round( ((config.previewOrder.length - currentState.response.missing_fields.length) / config.previewOrder.length) * 100, ), ) : 18; const statCards = useMemo( () => [ { stats: currentState.response?.status_fa ?? "شروع نشده", title: "وضعیت پردازش", avatarIcon: statusMeta.icon, avatarColor: statusMeta.color as ThemeColor, }, { stats: formatNumber(currentState.response?.missing_fields.length ?? 0), title: "فیلدهای ناقص", avatarIcon: "tabler-help-circle", avatarColor: "warning" as ThemeColor, }, { stats: formatNumber(config.getItemCount(planPreview)), title: config.itemCountTitle, avatarIcon: config.icon, avatarColor: "success" as ThemeColor, }, ], [ config, currentState.response?.missing_fields.length, currentState.response?.status_fa, planPreview, statusMeta.color, statusMeta.icon, ], ); const updateTabState = ( tab: ParserTabKey, updater: Partial | ((previous: TabState) => TabState), ) => { setTabStates((previous) => ({ ...previous, [tab]: typeof updater === "function" ? updater(previous[tab]) : { ...previous[tab], ...updater }, })); }; const handleReset = (tab: ParserTabKey) => { updateTabState(tab, createInitialTabState()); }; const submitRequest = async ( tab: ParserTabKey, payload: Record, ) => { const tabConfig = PARSER_CONFIGS[tab]; updateTabState(tab, (previous) => ({ ...previous, loading: true, requestError: null, statusNote: null, })); try { const nextResponse = await tabConfig.submit(payload); updateTabState(tab, (previous) => ({ ...previous, response: nextResponse, answers: buildNextAnswersState( nextResponse, previous.answers, tabConfig, ), activeQuestion: 0, statusNote: nextResponse.status === "completed" ? `برنامه ${tabConfig.label} نهایی آماده شد و می توانی آن را با تیم مزرعه به اشتراک بگذاری.` : "سیستم چند ابهام پیدا کرده؛ جواب ها را کامل کن تا JSON نهایی ساخته شود.", })); } catch (error) { updateTabState(tab, (previous) => ({ ...previous, requestError: extractErrorMessage( error, `در ساخت برنامه ${tabConfig.label} مشکلی پیش آمد. دوباره تلاش کن.`, ), })); } finally { updateTabState(tab, (previous) => ({ ...previous, loading: false, })); } }; const handleGeneratePlan = async () => { const trimmedMessage = currentState.message.trim(); if (!trimmedMessage) { updateTabState(activeTab, (previous) => ({ ...previous, requestError: `اول متن برنامه ${config.label} را بنویس تا تحلیل را شروع کنیم.`, })); return; } await submitRequest( activeTab, config.buildPayload({ message: trimmedMessage, farmUuid, }), ); }; const handleSubmitAnswers = async () => { if (!currentState.response) { return; } const normalizedAnswers = normalizeAnswers( currentState.response.questions, currentState.answers, ); const unansweredQuestion = currentState.response.questions.find( (question) => { const value = normalizedAnswers[question.field]; return value === undefined || value === null || value === ""; }, ); if (unansweredQuestion) { updateTabState(activeTab, (previous) => ({ ...previous, requestError: `پاسخ سوال «${config.fieldLabels[unansweredQuestion.field] ?? unansweredQuestion.question}» هنوز کامل نشده است.`, })); return; } await submitRequest( activeTab, config.buildPayload({ answers: normalizedAnswers, partialPlan: currentState.response.collected_data, farmUuid, }), ); }; const handleCopyJson = async () => { if ( !planPreview || typeof navigator === "undefined" || !navigator.clipboard ) { updateTabState(activeTab, (previous) => ({ ...previous, statusNote: "کپی خودکار روی این مرورگر در دسترس نیست.", })); return; } await navigator.clipboard.writeText(createJsonSnapshot(planPreview)); updateTabState(activeTab, (previous) => ({ ...previous, statusNote: `نسخه JSON برنامه ${config.label} در کلیپ بورد کپی شد.`, })); }; return ( {config.heroTitle} {config.heroDescription} موتور تحلیل Free-text parser + clarification flow مزرعه فعال {farmHub?.name || farmUuid || "بدون مزرعه فعال"} {singleTabMode ? `جریان هوشمند ${config.label}` : "دو جریان هوشمند، یک صفحه واحد"} {singleTabMode ? `ورودی متنی برنامه ${config.label} را بفرست، ابهام ها را کامل کن و خروجی JSON نهایی بگیر.` : "بین تب آبیاری و تب کودهی جابه جا شو و هر کدام را با همان flow سوال های تکمیلی تا JSON نهایی پیش ببر."} متن آزاد تکمیل ابهام ها برنامه نهایی {!singleTabMode ? ( setActiveTab(value)} variant="fullWidth" sx={{ p: 1, borderRadius: 4, backgroundColor: alpha(theme.palette.primary.main, 0.05), minHeight: 64, "& .MuiTabs-indicator": { display: "none" }, }} > {availableTabs.map((tabKey) => { const tabConfig = PARSER_CONFIGS[tabKey]; return ( } iconPosition="start" label={tabConfig.label} sx={{ minHeight: 54, borderRadius: 3, fontWeight: 700, transition: "all 0.2s ease", "&.Mui-selected": { color: theme.palette.common.white, background: `linear-gradient(135deg, ${theme.palette.primary.main} 0%, ${theme.palette.info.main} 100%)`, boxShadow: `0 14px 32px ${alpha(theme.palette.primary.main, 0.24)}`, }, }} /> ); })} ) : null} {config.panelTitle} {config.panelDescription} updateTabState(activeTab, (previous) => ({ ...previous, message: config.samplePrompts[0], })), }, }, { text: "کپی JSON", icon: "tabler-copy", menuItemProps: { onClick: handleCopyJson, }, }, { text: "شروع دوباره", icon: "tabler-rotate-clockwise-2", menuItemProps: { onClick: () => handleReset(activeTab), }, }, ]} /> {!farmUuid && ( مزرعه فعالی پیدا نشد. صفحه هنوز کار می کند، اما اگر `farm_uuid` فعال باشد پاسخ API دقیق تر می شود. )} updateTabState(activeTab, (previous) => ({ ...previous, message: event.target.value, })) } /> نمونه های آماده برای شروع سریع {config.samplePrompts.map((prompt) => ( updateTabState(activeTab, (previous) => ({ ...previous, message: prompt, })) } variant="outlined" sx={{ justifyContent: "flex-start", px: 1, py: 2.75, maxWidth: "100%", height: "auto", borderRadius: 3, backgroundColor: alpha( theme.palette.success.main, 0.05, ), "& .MuiChip-label": { display: "block", whiteSpace: "normal", }, }} /> ))} {currentState.loading && ( )} {currentState.requestError && ( {currentState.requestError} )} {currentState.statusNote && ( {currentState.statusNote} )} {currentState.response && ( {currentState.response.status_fa} {currentState.response.summary} {currentState.response.status === "needs_clarification" && currentState.response.questions.length > 0 && currentQuestion && ( مرحله تکمیل ابهام ها سوال ها را یکی یکی جلو ببر؛ بعد از تکمیل، همان endpoint با `answers` و `partial_plan` دوباره صدا زده می شود. {currentState.response.questions.map( (question) => ( {config.fieldLabels[question.field] ?? question.field} ), )} سوال{" "} {formatNumber( currentState.activeQuestion + 1, )}{" "} از{" "} {formatNumber( currentState.response.questions .length, )} {config.fieldLabels[ currentQuestion.field ] ?? currentQuestion.field} {currentQuestion.question} {currentQuestion.rationale} updateTabState( activeTab, (previous) => ({ ...previous, answers: { ...previous.answers, [currentQuestion.field]: event.target.value, }, }), ) } placeholder="پاسخ را اینجا بنویس..." /> )} )} {statCards.map((card) => ( ))} پیش نمایش زنده داده ساختاریافته این بخش نشان می دهد سیستم تا این لحظه چه چیزهایی را فهمیده است. درصد تکمیل برنامه {formatNumber(completionValue)}٪ {planPreview ? ( {config.previewOrder.map((field) => { const value = config.getFieldValue(planPreview, field); return ( {config.fieldLabels[field] ?? field} {config.formatFieldValue(field, value)} ); })} ) : ( هنوز خروجی ای نداریم. متن را ارسال کن تا collected_data و سپس final_plan اینجا شکل بگیرد. )} {primaryItem !== null && primaryItem !== undefined && ( {config.primaryCardTitle} {config.getPrimaryHeadline(primaryItem) || config.primaryFallbackTitle} {config.getPrimaryMeta(primaryItem).map((item) => ( {item.label} {item.value || "—"} ))} )} {currentState.response?.final_plan && ( {config.finalCardTitle} {config.finalCardDescription} {createJsonSnapshot(currentState.response.final_plan)} )} ); }; export default FertilizationPlanParserPage;