|
1 | 1 | import { ConstsOwnerType, ConstsTaskStatus, type DomainModel, type DomainProjectTask, type DomainVMPort } from "@/api/Api" |
| 2 | +import { useAppRuntime } from "@/components/app-runtime-provider" |
2 | 3 | import { useBreadcrumbTask } from "@/components/console/breadcrumb-task-context" |
3 | 4 | import { useCommonData } from "@/components/console/data-provider" |
4 | 5 | import { PlanStepsBlock } from "@/components/console/task/chat-panel" |
@@ -62,13 +63,15 @@ const BUILTIN_TASK_MODEL_OPTIONS = [ |
62 | 63 | { model: "monkeycode-pro", labelKey: "pro", badgeKey: "proBadge", badgeVariant: "secondary" as const, iconName: "vip-1" }, |
63 | 64 | { model: "monkeycode-ultra", labelKey: "ultra", badgeKey: "ultraBadge", badgeVariant: "secondary" as const, iconName: "vip-2" }, |
64 | 65 | ] as const |
| 66 | +type BuiltinTaskModelName = typeof BUILTIN_TASK_MODEL_OPTIONS[number]["model"] |
65 | 67 | const OPEN_WALLET_DIALOG_EVENT = "open-wallet-dialog" |
66 | 68 | type MessageSource = "live" | "history" |
67 | 69 | const MODEL_SWITCH_MIN_CREATED_AT = 1777381200 // 2026-04-28 21:00:00 +08:00 |
68 | 70 |
|
69 | 71 | export default function TaskDetailPage() { |
70 | 72 | const { taskId } = useParams() |
71 | 73 | const { setTaskName } = useBreadcrumbTask() ?? {} |
| 74 | + const { serverConfig } = useAppRuntime() |
72 | 75 | const { models, loadingModels, subscription } = useCommonData() |
73 | 76 | const { t } = useTranslation() |
74 | 77 | const isMobile = useIsMobile() |
@@ -133,6 +136,7 @@ export default function TaskDetailPage() { |
133 | 136 | const previewDialogOpenRef = React.useRef(false) |
134 | 137 | const showPreparing = useShouldShowPreparing(task) |
135 | 138 | const taskInteractive = task?.status === ConstsTaskStatus.TaskStatusProcessing |
| 139 | + const canPublishWebsite = !IS_OFFLINE_EDITION && serverConfig?.region === "cn" |
136 | 140 | const envid = task?.virtualmachine?.id |
137 | 141 | const cancelledRef = React.useRef(false) |
138 | 142 | const timeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null) |
@@ -274,6 +278,32 @@ export default function TaskDetailPage() { |
274 | 278 | () => models.filter((model) => model.id || model.model), |
275 | 279 | [models] |
276 | 280 | ) |
| 281 | + const recommendedModelKeys = React.useMemo(() => { |
| 282 | + return BUILTIN_TASK_MODEL_OPTIONS.reduce((recommended, option) => { |
| 283 | + const recommendedModel = supportedModels |
| 284 | + .filter((model) => getBuiltinModelName(model.model) === option.model) |
| 285 | + .sort((left, right) => { |
| 286 | + const weightDiff = (right.weight || 0) - (left.weight || 0) |
| 287 | + if (weightDiff !== 0) { |
| 288 | + return weightDiff |
| 289 | + } |
| 290 | + |
| 291 | + const nameDiff = (left.model || "").localeCompare(right.model || "") |
| 292 | + if (nameDiff !== 0) { |
| 293 | + return nameDiff |
| 294 | + } |
| 295 | + |
| 296 | + return (left.id || "").localeCompare(right.id || "") |
| 297 | + })[0] |
| 298 | + |
| 299 | + const recommendedKey = recommendedModel?.id || recommendedModel?.model |
| 300 | + if (recommendedKey) { |
| 301 | + recommended[option.model] = recommendedKey |
| 302 | + } |
| 303 | + |
| 304 | + return recommended |
| 305 | + }, {} as Partial<Record<BuiltinTaskModelName, string>>) |
| 306 | + }, [supportedModels]) |
277 | 307 | const modelGroups = React.useMemo(() => { |
278 | 308 | const builtinModelGroups = IS_OFFLINE_EDITION |
279 | 309 | ? [] |
@@ -774,34 +804,26 @@ export default function TaskDetailPage() { |
774 | 804 | return currentModel ? getModelOptionDisplayName(currentModel) : getModelDisplayName(currentModelName) |
775 | 805 | }, [builtinTaskModelOptions, currentModel, currentModelName, getModelOptionDisplayName, isMobile]) |
776 | 806 |
|
777 | | - const getRecommendedModelBadge = React.useCallback((modelName?: string | null) => { |
778 | | - const normalizedModelName = modelName?.trim().toLowerCase() |
779 | | - if (!normalizedModelName) { |
| 807 | + const getRecommendedModelBadge = React.useCallback((model: DomainModel) => { |
| 808 | + const builtinModelName = getBuiltinModelName(model.model) |
| 809 | + if (!builtinModelName) { |
780 | 810 | return null |
781 | 811 | } |
782 | 812 |
|
783 | | - const builtinModelName = getBuiltinModelName(normalizedModelName) |
784 | | - const nestedModelName = builtinModelName |
785 | | - ? normalizedModelName.slice(builtinModelName.length).replace(/^\/+/, "") |
786 | | - : normalizedModelName |
787 | | - |
788 | | - if ( |
789 | | - (builtinModelName === "monkeycode-basic" && nestedModelName === "qwen3.5-plus") |
790 | | - || (builtinModelName === "monkeycode-pro" && nestedModelName === "qwen3.6-plus") |
791 | | - || (builtinModelName === "monkeycode-ultra" && nestedModelName === "gpt-5.5") |
792 | | - ) { |
| 813 | + const modelKey = model.id || model.model |
| 814 | + if (modelKey && recommendedModelKeys[builtinModelName] === modelKey) { |
793 | 815 | return t("taskDetail.page.models.recommended") |
794 | 816 | } |
795 | 817 |
|
796 | 818 | return null |
797 | | - }, [t]) |
| 819 | + }, [recommendedModelKeys, t]) |
798 | 820 |
|
799 | 821 | const renderModelSwitchOption = React.useCallback((model: DomainModel, nested = false, indented = false) => { |
800 | 822 | const modelName = model.model || t("taskDetail.page.models.unknown") |
801 | 823 | const isSelected = model.id === currentModelId || (!currentModelId && model.model === currentModelName) |
802 | 824 | const canUseModel = canUseModelBySubscription(model, subscription) |
803 | 825 | const displayName = getModelOptionDisplayName(model, nested) |
804 | | - const recommendedBadge = getRecommendedModelBadge(model.model) |
| 826 | + const recommendedBadge = getRecommendedModelBadge(model) |
805 | 827 |
|
806 | 828 | return ( |
807 | 829 | <DropdownMenuRadioItem |
@@ -1294,7 +1316,7 @@ export default function TaskDetailPage() { |
1294 | 1316 | <IconDeviceDesktop className="size-3.5" /> |
1295 | 1317 | {t("taskDetail.panels.preview")}{previewPortCount > 0 ? ` (${previewPortCount})` : ""} |
1296 | 1318 | </Button> |
1297 | | - {!IS_OFFLINE_EDITION && ( |
| 1319 | + {canPublishWebsite && ( |
1298 | 1320 | <Button |
1299 | 1321 | variant="ghost" |
1300 | 1322 | size="sm" |
@@ -1530,24 +1552,26 @@ export default function TaskDetailPage() { |
1530 | 1552 | /> |
1531 | 1553 | </DialogContent> |
1532 | 1554 | </Dialog> |
1533 | | - <Dialog open={publishConfirmDialogOpen} onOpenChange={setPublishConfirmDialogOpen}> |
1534 | | - <DialogContent> |
1535 | | - <DialogHeader> |
1536 | | - <DialogTitle>{t("taskDetail.page.dialogs.publishWebsite.title")}</DialogTitle> |
1537 | | - <DialogDescription> |
1538 | | - {t("taskDetail.page.dialogs.publishWebsite.description")} |
1539 | | - </DialogDescription> |
1540 | | - </DialogHeader> |
1541 | | - <DialogFooter> |
1542 | | - <Button variant="outline" onClick={() => setPublishConfirmDialogOpen(false)}> |
1543 | | - {t("taskDetail.common.cancel")} |
1544 | | - </Button> |
1545 | | - <Button onClick={handleConfirmPublishWebsite}> |
1546 | | - {t("taskDetail.page.dialogs.publishWebsite.confirm")} |
1547 | | - </Button> |
1548 | | - </DialogFooter> |
1549 | | - </DialogContent> |
1550 | | - </Dialog> |
| 1555 | + {canPublishWebsite && ( |
| 1556 | + <Dialog open={publishConfirmDialogOpen} onOpenChange={setPublishConfirmDialogOpen}> |
| 1557 | + <DialogContent> |
| 1558 | + <DialogHeader> |
| 1559 | + <DialogTitle>{t("taskDetail.page.dialogs.publishWebsite.title")}</DialogTitle> |
| 1560 | + <DialogDescription> |
| 1561 | + {t("taskDetail.page.dialogs.publishWebsite.description")} |
| 1562 | + </DialogDescription> |
| 1563 | + </DialogHeader> |
| 1564 | + <DialogFooter> |
| 1565 | + <Button variant="outline" onClick={() => setPublishConfirmDialogOpen(false)}> |
| 1566 | + {t("taskDetail.common.cancel")} |
| 1567 | + </Button> |
| 1568 | + <Button onClick={handleConfirmPublishWebsite}> |
| 1569 | + {t("taskDetail.page.dialogs.publishWebsite.confirm")} |
| 1570 | + </Button> |
| 1571 | + </DialogFooter> |
| 1572 | + </DialogContent> |
| 1573 | + </Dialog> |
| 1574 | + )} |
1551 | 1575 | </div> |
1552 | 1576 | ) |
1553 | 1577 | } |
0 commit comments