|
1 | | -import { ConstsGitPlatform, type DomainBranch, type DomainModel, type DomainProject, type DomainSubscriptionResp } from "@/api/Api" |
| 1 | +import { ConstsGitPlatform, ConstsHostStatus, ConstsOwnerType, type DomainBranch, type DomainHost, type DomainImage, type DomainModel, type DomainProject, type DomainSubscriptionResp } from "@/api/Api" |
| 2 | +import Icon from "@/components/common/Icon" |
| 3 | +import { Badge } from "@/components/ui/badge" |
2 | 4 | import { Input } from "@/components/ui/input" |
3 | 5 | import { Label } from "@/components/ui/label" |
4 | 6 | import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" |
5 | 7 | import { Spinner } from "@/components/ui/spinner" |
| 8 | +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip" |
6 | 9 | import ModelSelect from "@/components/console/task/model-select" |
7 | | -import { selectPreferredTaskModel } from "@/utils/common" |
| 10 | +import { getHostBadges, getImageShortName, getOSFromImageName, getOwnerTypeBadge, selectHost, selectImage, selectPreferredTaskModel } from "@/utils/common" |
| 11 | +import { IS_OFFLINE_EDITION } from "@/utils/edition" |
8 | 12 | import { apiRequest } from "@/utils/requestUtils" |
9 | 13 | import { useCallback, useEffect, useMemo, useRef, useState } from "react" |
10 | 14 | import { toast } from "sonner" |
@@ -62,6 +66,76 @@ export function useIssueTaskModelSelection( |
62 | 66 | } |
63 | 67 | } |
64 | 68 |
|
| 69 | +export function useIssueTaskImageSelection( |
| 70 | + open: boolean, |
| 71 | + images: DomainImage[], |
| 72 | + projectImageId?: string, |
| 73 | +) { |
| 74 | + const [selectedImageId, setSelectedImageId] = useState("") |
| 75 | + |
| 76 | + useEffect(() => { |
| 77 | + if (!open) { |
| 78 | + setSelectedImageId("") |
| 79 | + return |
| 80 | + } |
| 81 | + |
| 82 | + const selectedImageIsValid = images.some((image) => image.id === selectedImageId) |
| 83 | + if (selectedImageIsValid) { |
| 84 | + return |
| 85 | + } |
| 86 | + |
| 87 | + const projectImageIsValid = !!projectImageId && images.some((image) => image.id === projectImageId) |
| 88 | + setSelectedImageId(projectImageIsValid ? projectImageId : selectImage(images, true)) |
| 89 | + }, [images, open, projectImageId, selectedImageId]) |
| 90 | + |
| 91 | + return { |
| 92 | + selectedImageId, |
| 93 | + setSelectedImageId, |
| 94 | + } |
| 95 | +} |
| 96 | + |
| 97 | +function getDefaultIssueTaskHostId(hosts: DomainHost[]) { |
| 98 | + if (IS_OFFLINE_EDITION) { |
| 99 | + return hosts.find((host) => host.id && host.status === ConstsHostStatus.HostStatusOnline)?.id || "" |
| 100 | + } |
| 101 | + |
| 102 | + return selectHost(hosts, false) |
| 103 | +} |
| 104 | + |
| 105 | +export function useIssueTaskHostSelection( |
| 106 | + open: boolean, |
| 107 | + hosts: DomainHost[], |
| 108 | + selectedModel?: DomainModel, |
| 109 | +) { |
| 110 | + const [selectedHostId, setSelectedHostId] = useState("") |
| 111 | + const selectedPublicModel = selectedModel?.owner?.type === ConstsOwnerType.OwnerTypePublic |
| 112 | + |
| 113 | + useEffect(() => { |
| 114 | + if (!open) { |
| 115 | + setSelectedHostId("") |
| 116 | + return |
| 117 | + } |
| 118 | + |
| 119 | + if (!IS_OFFLINE_EDITION && selectedPublicModel) { |
| 120 | + setSelectedHostId("public_host") |
| 121 | + return |
| 122 | + } |
| 123 | + |
| 124 | + const selectedHostIsValid = selectedHostId === "public_host" |
| 125 | + ? !IS_OFFLINE_EDITION |
| 126 | + : hosts.some((host) => host.id === selectedHostId && host.status === ConstsHostStatus.HostStatusOnline) |
| 127 | + |
| 128 | + if (!selectedHostIsValid) { |
| 129 | + setSelectedHostId(getDefaultIssueTaskHostId(hosts)) |
| 130 | + } |
| 131 | + }, [hosts, open, selectedHostId, selectedPublicModel]) |
| 132 | + |
| 133 | + return { |
| 134 | + selectedHostId, |
| 135 | + setSelectedHostId, |
| 136 | + } |
| 137 | +} |
| 138 | + |
65 | 139 | export function useProjectBranchSelection(open: boolean, project?: DomainProject) { |
66 | 140 | const { t } = useTranslation() |
67 | 141 | const [branches, setBranches] = useState<string[]>([]) |
@@ -248,3 +322,90 @@ export function IssueTaskModelSelect({ |
248 | 322 | </div> |
249 | 323 | ) |
250 | 324 | } |
| 325 | + |
| 326 | +export function IssueTaskImageSelect({ |
| 327 | + images, |
| 328 | + selectedImageId, |
| 329 | + setSelectedImageId, |
| 330 | +}: { |
| 331 | + images: DomainImage[] |
| 332 | + selectedImageId: string |
| 333 | + setSelectedImageId: (imageId: string) => void |
| 334 | +}) { |
| 335 | + const { t } = useTranslation() |
| 336 | + |
| 337 | + return ( |
| 338 | + <div className="space-y-2"> |
| 339 | + <Label>{t("consoleProject.issueTask.image")}</Label> |
| 340 | + <Select value={selectedImageId} onValueChange={setSelectedImageId}> |
| 341 | + <SelectTrigger className="w-full"> |
| 342 | + <SelectValue placeholder={t("consoleProject.issueTask.selectImage")} /> |
| 343 | + </SelectTrigger> |
| 344 | + <SelectContent> |
| 345 | + {images.filter((image) => image.id).map((image) => ( |
| 346 | + <SelectItem key={image.id} value={image.id!}> |
| 347 | + <Tooltip> |
| 348 | + <TooltipTrigger asChild> |
| 349 | + <div className="flex items-center gap-2"> |
| 350 | + <Icon name={getOSFromImageName(image.name || "")} className="size-4" /> |
| 351 | + <span>{image.remark || getImageShortName(image.name || "")}</span> |
| 352 | + {getOwnerTypeBadge(image.owner)} |
| 353 | + </div> |
| 354 | + </TooltipTrigger> |
| 355 | + <TooltipContent side="right">{image.name}</TooltipContent> |
| 356 | + </Tooltip> |
| 357 | + </SelectItem> |
| 358 | + ))} |
| 359 | + </SelectContent> |
| 360 | + </Select> |
| 361 | + </div> |
| 362 | + ) |
| 363 | +} |
| 364 | + |
| 365 | +export function IssueTaskHostSelect({ |
| 366 | + hosts, |
| 367 | + selectedHostId, |
| 368 | + selectedModel, |
| 369 | + setSelectedHostId, |
| 370 | +}: { |
| 371 | + hosts: DomainHost[] |
| 372 | + selectedHostId: string |
| 373 | + selectedModel?: DomainModel |
| 374 | + setSelectedHostId: (hostId: string) => void |
| 375 | +}) { |
| 376 | + const { t } = useTranslation() |
| 377 | + const selectedPublicModel = selectedModel?.owner?.type === ConstsOwnerType.OwnerTypePublic |
| 378 | + |
| 379 | + return ( |
| 380 | + <div className="space-y-2"> |
| 381 | + <Label>{t("consoleProject.issueTask.host")}</Label> |
| 382 | + <Select value={selectedHostId} onValueChange={setSelectedHostId}> |
| 383 | + <SelectTrigger className="w-full"> |
| 384 | + <SelectValue placeholder={t("consoleProject.issueTask.selectHost")} /> |
| 385 | + </SelectTrigger> |
| 386 | + <SelectContent> |
| 387 | + {!IS_OFFLINE_EDITION && ( |
| 388 | + <SelectItem value="public_host"> |
| 389 | + <div className="flex items-center gap-2"> |
| 390 | + <span>MonkeyCode</span> |
| 391 | + <Badge className="!text-primary-foreground">{t("consoleProject.issueTask.free")}</Badge> |
| 392 | + </div> |
| 393 | + </SelectItem> |
| 394 | + )} |
| 395 | + {hosts.filter((host) => host.id).map((host) => ( |
| 396 | + <SelectItem |
| 397 | + key={host.id} |
| 398 | + value={host.id!} |
| 399 | + disabled={host.status !== ConstsHostStatus.HostStatusOnline || (!IS_OFFLINE_EDITION && selectedPublicModel)} |
| 400 | + > |
| 401 | + <div className="flex items-center gap-2"> |
| 402 | + <span>{host.remark || `${host.name}-${host.external_ip}`}</span> |
| 403 | + {getHostBadges(host)} |
| 404 | + </div> |
| 405 | + </SelectItem> |
| 406 | + ))} |
| 407 | + </SelectContent> |
| 408 | + </Select> |
| 409 | + </div> |
| 410 | + ) |
| 411 | +} |
0 commit comments