flag impl fin
This commit is contained in:
@@ -4349,7 +4349,707 @@
|
|||||||
"wiki-distortion-mosaic",
|
"wiki-distortion-mosaic",
|
||||||
"wiki-dock-anthracnose",
|
"wiki-dock-anthracnose",
|
||||||
"wiki-dogwood-anthracnose",
|
"wiki-dogwood-anthracnose",
|
||||||
"wiki-dogwood-leaf-spot"
|
"wiki-dogwood-leaf-spot",
|
||||||
|
"wiki-dogwood-spot-anthracnose",
|
||||||
|
"wiki-dollar-spot",
|
||||||
|
"wiki-double-blossom",
|
||||||
|
"wiki-douglas-fir-needle-cast",
|
||||||
|
"wiki-douglas-fir-root-disease",
|
||||||
|
"wiki-downy-spot",
|
||||||
|
"wiki-drop",
|
||||||
|
"wiki-dropping",
|
||||||
|
"wiki-drought",
|
||||||
|
"wiki-dryberry-disease",
|
||||||
|
"wiki-dutch-elm-disease",
|
||||||
|
"wiki-dwarf-mistletoe",
|
||||||
|
"wiki-dwarf-mosaic",
|
||||||
|
"wiki-dwarfism",
|
||||||
|
"wiki-early-leaf-spot",
|
||||||
|
"wiki-early-leaf-spot-of-peanut",
|
||||||
|
"wiki-eggplant-little-leaf",
|
||||||
|
"wiki-eggplant-mosaic",
|
||||||
|
"wiki-eggplant-mottle-dwarf",
|
||||||
|
"wiki-eggplant-phomopsis",
|
||||||
|
"wiki-elderberry-leaf-spot",
|
||||||
|
"wiki-elderberry-virus",
|
||||||
|
"wiki-elephantiasis",
|
||||||
|
"wiki-elm-leaf-scorch",
|
||||||
|
"wiki-elm-mosaic",
|
||||||
|
"wiki-elm-mottle",
|
||||||
|
"wiki-elm-stripe",
|
||||||
|
"wiki-elm-witches-broom",
|
||||||
|
"wiki-elm-yellows",
|
||||||
|
"wiki-epicarp-lesion",
|
||||||
|
"wiki-esca",
|
||||||
|
"wiki-eucalyptus-leaf-spot",
|
||||||
|
"wiki-euonymus-anthracnose",
|
||||||
|
"wiki-euphorbia-mosaic",
|
||||||
|
"wiki-european-stone-fruit-yellows",
|
||||||
|
"wiki-evergreen-blackberry",
|
||||||
|
"wiki-exobasidium-leaf-spot",
|
||||||
|
"wiki-fabraea-leaf-spot",
|
||||||
|
"wiki-fabrea-leaf-spot",
|
||||||
|
"wiki-fairy-ring",
|
||||||
|
"wiki-fairy-ring-leaf-spot",
|
||||||
|
"wiki-false-blossom",
|
||||||
|
"wiki-false-loose-smut",
|
||||||
|
"wiki-false-root-knot-nematode",
|
||||||
|
"wiki-fanleaf",
|
||||||
|
"wiki-ferrugem-do-feijoeiro-comum",
|
||||||
|
"wiki-filbert-stunt",
|
||||||
|
"wiki-filiform",
|
||||||
|
"wiki-flavescence-doree",
|
||||||
|
"wiki-fleck",
|
||||||
|
"wiki-fleur",
|
||||||
|
"wiki-flyspeck-of-apple",
|
||||||
|
"wiki-foliar-nematode",
|
||||||
|
"wiki-foliar-nematodes",
|
||||||
|
"wiki-foliar-vein-yellowing",
|
||||||
|
"wiki-freckle",
|
||||||
|
"wiki-frosty-pod",
|
||||||
|
"wiki-fruit-blotch-of-apple",
|
||||||
|
"wiki-fruit-chimera",
|
||||||
|
"wiki-fruit-drop",
|
||||||
|
"wiki-fungal-brown-spot",
|
||||||
|
"wiki-fungal-scald",
|
||||||
|
"wiki-fusariosis",
|
||||||
|
"wiki-fusarium-bark-disease",
|
||||||
|
"wiki-fused-fingers",
|
||||||
|
"wiki-galls",
|
||||||
|
"wiki-giantism",
|
||||||
|
"wiki-gnomonia-leaf-spot",
|
||||||
|
"wiki-golden-mosaic",
|
||||||
|
"wiki-grape-rust",
|
||||||
|
"wiki-grassy-shoot",
|
||||||
|
"wiki-gray-mold-of-apple",
|
||||||
|
"wiki-gray-snow-mold",
|
||||||
|
"wiki-gray-streak",
|
||||||
|
"wiki-gray-wall",
|
||||||
|
"wiki-grease-spot",
|
||||||
|
"wiki-greasy-blotch",
|
||||||
|
"wiki-greasy-center",
|
||||||
|
"wiki-green-blotch",
|
||||||
|
"wiki-green-mosaic",
|
||||||
|
"wiki-green-ring",
|
||||||
|
"wiki-green-scale",
|
||||||
|
"wiki-greening",
|
||||||
|
"wiki-ground-nut-rosette",
|
||||||
|
"wiki-groundnut-leaf-spot",
|
||||||
|
"wiki-guinea-grass-mosaic",
|
||||||
|
"wiki-gummosis",
|
||||||
|
"wiki-hairy-nightshade",
|
||||||
|
"wiki-hazelnut-mosaic",
|
||||||
|
"wiki-hazelnut-yellows",
|
||||||
|
"wiki-head-mold",
|
||||||
|
"wiki-head-smut",
|
||||||
|
"wiki-heart-leaf-disorder",
|
||||||
|
"wiki-heart-leaf-unfurling-disorder",
|
||||||
|
"wiki-helenium-s",
|
||||||
|
"wiki-helminthosporium-leaf-spot",
|
||||||
|
"wiki-hemp-mosaic",
|
||||||
|
"wiki-hemp-streak",
|
||||||
|
"wiki-high-mat",
|
||||||
|
"wiki-holeus-spot",
|
||||||
|
"wiki-honey-fungus",
|
||||||
|
"wiki-hop-latent",
|
||||||
|
"wiki-hop-mosaic",
|
||||||
|
"wiki-horn-sprout",
|
||||||
|
"wiki-horse",
|
||||||
|
"wiki-huanglongbing",
|
||||||
|
"wiki-impatiens-necrotic-spot",
|
||||||
|
"wiki-indian-cassava-mosaic",
|
||||||
|
"wiki-infectious-bud-failure",
|
||||||
|
"wiki-japanese-beetle",
|
||||||
|
"wiki-joint-infection",
|
||||||
|
"wiki-joint-infection-of-apple",
|
||||||
|
"wiki-kalancho-mosaic",
|
||||||
|
"wiki-karnal-bunt",
|
||||||
|
"wiki-kernel-decay",
|
||||||
|
"wiki-kernel-molds",
|
||||||
|
"wiki-kernel-shrivel",
|
||||||
|
"wiki-kernel-spot",
|
||||||
|
"wiki-khamedj",
|
||||||
|
"wiki-knot",
|
||||||
|
"wiki-lace-leaf",
|
||||||
|
"wiki-late-leaf-rust",
|
||||||
|
"wiki-latex-eruption",
|
||||||
|
"wiki-leaf-and-bud-nematode",
|
||||||
|
"wiki-leaf-and-pod-spot",
|
||||||
|
"wiki-leaf-and-stem-nematode",
|
||||||
|
"wiki-leaf-anthracnose",
|
||||||
|
"wiki-leaf-bleaching",
|
||||||
|
"wiki-leaf-blister",
|
||||||
|
"wiki-leaf-crinkle",
|
||||||
|
"wiki-leaf-cupping",
|
||||||
|
"wiki-leaf-glazing",
|
||||||
|
"wiki-leaf-miner",
|
||||||
|
"wiki-leaf-mottle",
|
||||||
|
"wiki-leaf-pucker-of-apple",
|
||||||
|
"wiki-leaf-roll",
|
||||||
|
"wiki-leaf-scorch",
|
||||||
|
"wiki-leaf-streak",
|
||||||
|
"wiki-leaf-stripe",
|
||||||
|
"wiki-leaf-yellowing",
|
||||||
|
"wiki-leak",
|
||||||
|
"wiki-lettuce-infectious-yellows",
|
||||||
|
"wiki-lettuce-mosaic",
|
||||||
|
"wiki-lichen",
|
||||||
|
"wiki-light-leaf-spot",
|
||||||
|
"wiki-lime-blotch",
|
||||||
|
"wiki-line-pattern",
|
||||||
|
"wiki-lint-contamination",
|
||||||
|
"wiki-little-cherry",
|
||||||
|
"wiki-liver-spot",
|
||||||
|
"wiki-lixa-pequena",
|
||||||
|
"wiki-loose-kernel-smut",
|
||||||
|
"wiki-lophodermium-leaf-spot",
|
||||||
|
"wiki-maize-bushy-stunt",
|
||||||
|
"wiki-maize-chlorotic-dwarf",
|
||||||
|
"wiki-maize-chlorotic-mottle",
|
||||||
|
"wiki-maize-dwarf-mosaic",
|
||||||
|
"wiki-maize-mosaic",
|
||||||
|
"wiki-maize-rayado-fino",
|
||||||
|
"wiki-maize-rough-dwarf",
|
||||||
|
"wiki-maize-stripe",
|
||||||
|
"wiki-maize-tassel-abortion",
|
||||||
|
"wiki-maize-white-line-mosaic",
|
||||||
|
"wiki-maize-yellow-stripe",
|
||||||
|
"wiki-malayan-leaf-spot",
|
||||||
|
"wiki-mancha-angular",
|
||||||
|
"wiki-mango-scale",
|
||||||
|
"wiki-marginal-leaf-burn",
|
||||||
|
"wiki-maturity-bronzing",
|
||||||
|
"wiki-mealiness",
|
||||||
|
"wiki-mealybug",
|
||||||
|
"wiki-mela",
|
||||||
|
"wiki-mid-vein-spot",
|
||||||
|
"wiki-mild-mosaic",
|
||||||
|
"wiki-mildew",
|
||||||
|
"wiki-milo-disease",
|
||||||
|
"wiki-minor-leaf-spot",
|
||||||
|
"wiki-mistletoe",
|
||||||
|
"wiki-mofo-branco",
|
||||||
|
"wiki-moko",
|
||||||
|
"wiki-mold",
|
||||||
|
"wiki-moldy-core",
|
||||||
|
"wiki-monilia",
|
||||||
|
"wiki-moorpark-mottle",
|
||||||
|
"wiki-mop-top",
|
||||||
|
"wiki-mosaic-disease",
|
||||||
|
"wiki-mosaic-or-ringspot",
|
||||||
|
"wiki-mottle-leaf",
|
||||||
|
"wiki-mouse-ear",
|
||||||
|
"wiki-murcha-de-fusario",
|
||||||
|
"wiki-mycorrhiza",
|
||||||
|
"wiki-myriogenospora-leaf-binding",
|
||||||
|
"wiki-myrothecium-leaf-spot",
|
||||||
|
"wiki-mystrosporium-leaf-spot",
|
||||||
|
"wiki-navel-orange-worm",
|
||||||
|
"wiki-neck-blast",
|
||||||
|
"wiki-necrosis",
|
||||||
|
"wiki-necrotic-leaf-blotch",
|
||||||
|
"wiki-necrotic-leaf-blotch-of-apple",
|
||||||
|
"wiki-necrotic-ring-spot",
|
||||||
|
"wiki-necrotic-ringspot",
|
||||||
|
"wiki-nematode",
|
||||||
|
"wiki-net-blotch",
|
||||||
|
"wiki-nettlehead",
|
||||||
|
"wiki-noninfectious-bud-failure",
|
||||||
|
"wiki-northern-anthracnose",
|
||||||
|
"wiki-oak-root-fungus",
|
||||||
|
"wiki-oat-leaf-blotch",
|
||||||
|
"wiki-oat-mosaic",
|
||||||
|
"wiki-oat-necrotic-mottle",
|
||||||
|
"wiki-oat-red-leaf",
|
||||||
|
"wiki-oat-smut",
|
||||||
|
"wiki-oidio-do-feijoeiro-comum",
|
||||||
|
"wiki-olive-leaf-spot",
|
||||||
|
"wiki-orange-dog",
|
||||||
|
"wiki-orange-rust",
|
||||||
|
"wiki-other-nematodes",
|
||||||
|
"wiki-pale-leaf",
|
||||||
|
"wiki-palm-leaf-spot",
|
||||||
|
"wiki-panagrolaimus-nematode",
|
||||||
|
"wiki-panama-disease",
|
||||||
|
"wiki-panicum-mosaic",
|
||||||
|
"wiki-papaya-bunchy-top",
|
||||||
|
"wiki-pasmo",
|
||||||
|
"wiki-pea-enation-mosaic",
|
||||||
|
"wiki-pea-leaf-roll",
|
||||||
|
"wiki-pea-mosaic",
|
||||||
|
"wiki-pea-seed-borne-mosaic",
|
||||||
|
"wiki-pea-seedborne-mosaic",
|
||||||
|
"wiki-peach-leaf-curl",
|
||||||
|
"wiki-peach-mosaic-virus",
|
||||||
|
"wiki-peach-rosette",
|
||||||
|
"wiki-peach-scab",
|
||||||
|
"wiki-peach-yellow-bud-mosaic",
|
||||||
|
"wiki-peach-yellow-mottle",
|
||||||
|
"wiki-peach-yellows",
|
||||||
|
"wiki-peanut-mottle-virus",
|
||||||
|
"wiki-peanut-rust",
|
||||||
|
"wiki-pear-leaf-blister-mite",
|
||||||
|
"wiki-pear-leaf-curl",
|
||||||
|
"wiki-pear-leaf-spot",
|
||||||
|
"wiki-pear-psylla",
|
||||||
|
"wiki-pear-rust",
|
||||||
|
"wiki-pear-slug",
|
||||||
|
"wiki-pear-stony-pit",
|
||||||
|
"wiki-pearly-root",
|
||||||
|
"wiki-pecan-scab",
|
||||||
|
"wiki-pecky-rice",
|
||||||
|
"wiki-pelargonium-flower-break",
|
||||||
|
"wiki-pelargonium-line-pattern",
|
||||||
|
"wiki-pelargonium-ring-pattern",
|
||||||
|
"wiki-pelargonium-ringspot",
|
||||||
|
"wiki-pelargonium-zonale-spot",
|
||||||
|
"wiki-penicillium-mold",
|
||||||
|
"wiki-pepper-golden-mosaic",
|
||||||
|
"wiki-pepper-huasteco",
|
||||||
|
"wiki-pepper-leaf-curl",
|
||||||
|
"wiki-pepper-mottle",
|
||||||
|
"wiki-pepper-veinal-mottle",
|
||||||
|
"wiki-periconia-leaf-spot",
|
||||||
|
"wiki-persimmon",
|
||||||
|
"wiki-pestalosphaeria-leaf-spot",
|
||||||
|
"wiki-pestalotia-leaf-spot",
|
||||||
|
"wiki-petal-fall",
|
||||||
|
"wiki-peter-s-scorch",
|
||||||
|
"wiki-phaeoisariopsis-leaf-spot",
|
||||||
|
"wiki-phaeoseptoria-leaf-spot",
|
||||||
|
"wiki-phomopsis-cane-and-leaf-spot",
|
||||||
|
"wiki-phony-peach",
|
||||||
|
"wiki-phyllody",
|
||||||
|
"wiki-phylloxera",
|
||||||
|
"wiki-physoderma-brown-spot",
|
||||||
|
"wiki-phytophthora-leaf-fall",
|
||||||
|
"wiki-phytoplasma",
|
||||||
|
"wiki-pierce-s-disease",
|
||||||
|
"wiki-pin-sized-lesion",
|
||||||
|
"wiki-pink-snow-mold",
|
||||||
|
"wiki-pistachio",
|
||||||
|
"wiki-pitting",
|
||||||
|
"wiki-pleospora-leaf-spot",
|
||||||
|
"wiki-plum-pox",
|
||||||
|
"wiki-pod-mottle",
|
||||||
|
"wiki-podridao-cinzenta-do-caule",
|
||||||
|
"wiki-podridao-do-colo",
|
||||||
|
"wiki-podridao-radicular-de-rhizoctonia",
|
||||||
|
"wiki-podridao-radicular-seca",
|
||||||
|
"wiki-pokkah-boeng",
|
||||||
|
"wiki-postbloom-fruit-drop",
|
||||||
|
"wiki-potato-leaf-roll",
|
||||||
|
"wiki-potato-mop-top",
|
||||||
|
"wiki-potato-scab",
|
||||||
|
"wiki-potato-spindle-tuber-viroid",
|
||||||
|
"wiki-potato-x",
|
||||||
|
"wiki-potato-yellow-dwarf",
|
||||||
|
"wiki-potexvirus-unnamed",
|
||||||
|
"wiki-powdery-scab",
|
||||||
|
"wiki-primula-mosaic",
|
||||||
|
"wiki-primula-mottle",
|
||||||
|
"wiki-proliferation",
|
||||||
|
"wiki-prune-dwarf",
|
||||||
|
"wiki-prunus-necrotic-ringspot",
|
||||||
|
"wiki-pseudomonas-leaf-spot",
|
||||||
|
"wiki-pucciniastrum-leaf-rust",
|
||||||
|
"wiki-pucker-leaf",
|
||||||
|
"wiki-purple-blotch",
|
||||||
|
"wiki-purple-top",
|
||||||
|
"wiki-pyricularia-leaf-spot",
|
||||||
|
"wiki-pythium-damping-off",
|
||||||
|
"wiki-pythium-leak",
|
||||||
|
"wiki-radish-mosaic",
|
||||||
|
"wiki-rai-mosaic",
|
||||||
|
"wiki-ramularia-leaf-spot",
|
||||||
|
"wiki-raspberry",
|
||||||
|
"wiki-raspberry-bushy-dwarf",
|
||||||
|
"wiki-raspberry-leaf-curl",
|
||||||
|
"wiki-raspberry-leaf-spot",
|
||||||
|
"wiki-raspberry-mosaic",
|
||||||
|
"wiki-raspberry-veinbanding-mosaic",
|
||||||
|
"wiki-raspberry-yellow-dwarf",
|
||||||
|
"wiki-raspberry-yellow-spot",
|
||||||
|
"wiki-ratoon-stunting",
|
||||||
|
"wiki-rayadilla",
|
||||||
|
"wiki-red-blister",
|
||||||
|
"wiki-red-blotch",
|
||||||
|
"wiki-red-boot",
|
||||||
|
"wiki-red-leaf-blotch",
|
||||||
|
"wiki-red-leather-disease",
|
||||||
|
"wiki-red-spider",
|
||||||
|
"wiki-red-stripe",
|
||||||
|
"wiki-red-thread",
|
||||||
|
"wiki-redberry-disease",
|
||||||
|
"wiki-replant-problem",
|
||||||
|
"wiki-rhizoctonia-damping-off",
|
||||||
|
"wiki-rhizoctonia-soreshin",
|
||||||
|
"wiki-rhizomania",
|
||||||
|
"wiki-rhynchosia-little-leaf",
|
||||||
|
"wiki-rhynchosia-mosaic",
|
||||||
|
"wiki-rice-black-streaked-dwarf",
|
||||||
|
"wiki-rice-blast",
|
||||||
|
"wiki-rice-bunts",
|
||||||
|
"wiki-rice-dwarf",
|
||||||
|
"wiki-rice-grassy-stunt",
|
||||||
|
"wiki-rice-ragged-stunt",
|
||||||
|
"wiki-rice-transitory-yellowing",
|
||||||
|
"wiki-rice-tungro",
|
||||||
|
"wiki-rice-yellow-mottle",
|
||||||
|
"wiki-rind-disease",
|
||||||
|
"wiki-rind-spot",
|
||||||
|
"wiki-ringspot",
|
||||||
|
"wiki-ringspots",
|
||||||
|
"wiki-rna-virus",
|
||||||
|
"wiki-root-smallpox-disease",
|
||||||
|
"wiki-rose-leaf",
|
||||||
|
"wiki-rose-leaf-curl",
|
||||||
|
"wiki-rose-mosaic",
|
||||||
|
"wiki-rose-rosette",
|
||||||
|
"wiki-rosette-of-apple",
|
||||||
|
"wiki-rosetting",
|
||||||
|
"wiki-rough-dwarf",
|
||||||
|
"wiki-roxana",
|
||||||
|
"wiki-rubbery-wood-of-apple",
|
||||||
|
"wiki-rubus-stunt",
|
||||||
|
"wiki-russet",
|
||||||
|
"wiki-rust-mite",
|
||||||
|
"wiki-rust-white",
|
||||||
|
"wiki-rusty-spot",
|
||||||
|
"wiki-rye-ergot",
|
||||||
|
"wiki-satellite-panicum-mosaic",
|
||||||
|
"wiki-scab-of-apple",
|
||||||
|
"wiki-scab-of-citrus",
|
||||||
|
"wiki-scab-of-peach",
|
||||||
|
"wiki-scald-of-apple",
|
||||||
|
"wiki-sclerotinia",
|
||||||
|
"wiki-scopulariopsis-leaf-spot",
|
||||||
|
"wiki-scorch",
|
||||||
|
"wiki-seedling-disease",
|
||||||
|
"wiki-seedling-rust",
|
||||||
|
"wiki-segmented-banana",
|
||||||
|
"wiki-septoria-brown-spot",
|
||||||
|
"wiki-septoria-leaf-blotch",
|
||||||
|
"wiki-septoria-nodorum-blotch",
|
||||||
|
"wiki-septoria-tritici-blotch",
|
||||||
|
"wiki-shenandoah",
|
||||||
|
"wiki-shothole",
|
||||||
|
"wiki-silver-leaf-of-apple",
|
||||||
|
"wiki-silvering-disease",
|
||||||
|
"wiki-sinaloa-tomato-leaf-curl",
|
||||||
|
"wiki-slime-flux",
|
||||||
|
"wiki-slime-mold",
|
||||||
|
"wiki-small-hop",
|
||||||
|
"wiki-smut",
|
||||||
|
"wiki-snow-mold",
|
||||||
|
"wiki-snow-scald",
|
||||||
|
"wiki-soilborne-mosaic",
|
||||||
|
"wiki-sooty-blotch-of-apple",
|
||||||
|
"wiki-sore-shin",
|
||||||
|
"wiki-southern-anthracnose",
|
||||||
|
"wiki-southern-root-knot-nematode",
|
||||||
|
"wiki-southern-rust",
|
||||||
|
"wiki-speckle",
|
||||||
|
"wiki-speckled-blotch",
|
||||||
|
"wiki-speckled-leaf-blotch",
|
||||||
|
"wiki-speckled-snow-mold",
|
||||||
|
"wiki-sphacelia",
|
||||||
|
"wiki-sphaerotheca-pannosa",
|
||||||
|
"wiki-spike",
|
||||||
|
"wiki-spike-leaf",
|
||||||
|
"wiki-spiral-nematode-root-damage",
|
||||||
|
"wiki-split-peel",
|
||||||
|
"wiki-spot-anthracnose",
|
||||||
|
"wiki-spraing",
|
||||||
|
"wiki-spring-black-stem",
|
||||||
|
"wiki-squirter",
|
||||||
|
"wiki-stackburn",
|
||||||
|
"wiki-stagonospora-blotch",
|
||||||
|
"wiki-stagonospora-leaf-spot",
|
||||||
|
"wiki-stalk-smut",
|
||||||
|
"wiki-stem-and-bulb-nematode",
|
||||||
|
"wiki-stem-break",
|
||||||
|
"wiki-stem-nematode",
|
||||||
|
"wiki-stem-pitting",
|
||||||
|
"wiki-stem-pitting-of-apple",
|
||||||
|
"wiki-stem-rust",
|
||||||
|
"wiki-stem-smut",
|
||||||
|
"wiki-stem-splitting",
|
||||||
|
"wiki-stem-streak",
|
||||||
|
"wiki-stem-twisting-and-bending",
|
||||||
|
"wiki-stemphylium-leaf-and-stem-spot",
|
||||||
|
"wiki-sterility-mosaic",
|
||||||
|
"wiki-sticky-disease",
|
||||||
|
"wiki-stigmatomycosis",
|
||||||
|
"wiki-stippling",
|
||||||
|
"wiki-stolon-decay",
|
||||||
|
"wiki-stone-pitting",
|
||||||
|
"wiki-stony-pit",
|
||||||
|
"wiki-storage-mold",
|
||||||
|
"wiki-strawberry",
|
||||||
|
"wiki-strawberry-leaf-roll",
|
||||||
|
"wiki-strawberry-leaf-spot",
|
||||||
|
"wiki-strawberry-yellow-edge",
|
||||||
|
"wiki-strawbreaker",
|
||||||
|
"wiki-streak",
|
||||||
|
"wiki-striatura-ulcerosa",
|
||||||
|
"wiki-stripe",
|
||||||
|
"wiki-stripe-rust",
|
||||||
|
"wiki-stubby-root",
|
||||||
|
"wiki-stylet-nematode",
|
||||||
|
"wiki-sudden-death",
|
||||||
|
"wiki-sugar-end",
|
||||||
|
"wiki-sugarcane-mosaic",
|
||||||
|
"wiki-sulfur-deficiency",
|
||||||
|
"wiki-summer-black-stem",
|
||||||
|
"wiki-sun-blotch",
|
||||||
|
"wiki-sun-scald",
|
||||||
|
"wiki-sunblotch",
|
||||||
|
"wiki-superelongation",
|
||||||
|
"wiki-sweet-potato",
|
||||||
|
"wiki-sweet-potato-virus-disease",
|
||||||
|
"wiki-sycamore",
|
||||||
|
"wiki-taches-brunes",
|
||||||
|
"wiki-taiwan-marginal-scorch",
|
||||||
|
"wiki-take-all",
|
||||||
|
"wiki-tan-spot",
|
||||||
|
"wiki-tatter-leaf",
|
||||||
|
"wiki-tea",
|
||||||
|
"wiki-terminal-mottle",
|
||||||
|
"wiki-thrips",
|
||||||
|
"wiki-tikka-disease",
|
||||||
|
"wiki-tilletia",
|
||||||
|
"wiki-tip-over",
|
||||||
|
"wiki-tobacco",
|
||||||
|
"wiki-tobacco-ringspot",
|
||||||
|
"wiki-tobacco-ringspot-virus",
|
||||||
|
"wiki-tomato",
|
||||||
|
"wiki-tomato-aspermy",
|
||||||
|
"wiki-tomato-leaf-curl",
|
||||||
|
"wiki-tomato-ringspot",
|
||||||
|
"wiki-top-spotting",
|
||||||
|
"wiki-tristeza",
|
||||||
|
"wiki-tropical-rust",
|
||||||
|
"wiki-trunk-pitting",
|
||||||
|
"wiki-turnip-mosaic",
|
||||||
|
"wiki-twig-die-back",
|
||||||
|
"wiki-vein-clearing",
|
||||||
|
"wiki-vein-enation",
|
||||||
|
"wiki-vein-spot",
|
||||||
|
"wiki-veinbanding",
|
||||||
|
"wiki-verbena-latent",
|
||||||
|
"wiki-water-mold",
|
||||||
|
"wiki-web-blotch",
|
||||||
|
"wiki-weevil",
|
||||||
|
"wiki-western-x-disease",
|
||||||
|
"wiki-wet-feet",
|
||||||
|
"wiki-wheat-curl-mite",
|
||||||
|
"wiki-wheat-soil-borne-mosaic",
|
||||||
|
"wiki-white-blister",
|
||||||
|
"wiki-white-bract",
|
||||||
|
"wiki-white-head",
|
||||||
|
"wiki-white-pine-blister-rust",
|
||||||
|
"wiki-white-thread",
|
||||||
|
"wiki-wilt-of-fruit-bunches",
|
||||||
|
"wiki-winter-injury",
|
||||||
|
"wiki-wire-stem",
|
||||||
|
"wiki-wirestem",
|
||||||
|
"wiki-wirrega-blotch",
|
||||||
|
"wiki-witch-s-broom",
|
||||||
|
"wiki-witches-broom-of-apple",
|
||||||
|
"wiki-withertip",
|
||||||
|
"wiki-wood-decay",
|
||||||
|
"wiki-wood-pocket",
|
||||||
|
"wiki-wood-stains",
|
||||||
|
"wiki-woolly-apple-aphid",
|
||||||
|
"wiki-x-disease",
|
||||||
|
"wiki-xanthomonas-leaf-spot",
|
||||||
|
"wiki-xyloporosis",
|
||||||
|
"wiki-xyloporosis-of-apple",
|
||||||
|
"wiki-yellow-edge",
|
||||||
|
"wiki-yellow-leaf-curl",
|
||||||
|
"wiki-yellow-leaf-spot",
|
||||||
|
"wiki-yellow-mat",
|
||||||
|
"wiki-yellow-mosaic",
|
||||||
|
"wiki-yellow-mottle",
|
||||||
|
"wiki-yellow-net-vein",
|
||||||
|
"wiki-yellow-pulp",
|
||||||
|
"wiki-yellow-shoot",
|
||||||
|
"wiki-yellow-sigatoka",
|
||||||
|
"wiki-yellow-stunt",
|
||||||
|
"wiki-yellowing",
|
||||||
|
"wiki-zebra-chip",
|
||||||
|
"wiki-zonate-eye-spot",
|
||||||
|
"tomato-tomato-fruitworm",
|
||||||
|
"tomato-tomato-hornworm",
|
||||||
|
"tomato-tobacco-hornworm",
|
||||||
|
"tomato-brown-tipped-pearl",
|
||||||
|
"tomato-eggplant-borer",
|
||||||
|
"tomato-tomato-fruit-borer",
|
||||||
|
"tomato-eggplant-leafroller",
|
||||||
|
"tomato-potato-tuber-moth",
|
||||||
|
"tomato-tomato-borer",
|
||||||
|
"tomato-tomato-pinworm",
|
||||||
|
"tomato-root-knot",
|
||||||
|
"tomato-sting",
|
||||||
|
"tomato-stubby-root",
|
||||||
|
"tomato-autogenous-necrosis",
|
||||||
|
"tomato-fruit-pox",
|
||||||
|
"tomato-gold-fleck",
|
||||||
|
"tomato-graywall",
|
||||||
|
"potato-potato-cyst-nematode",
|
||||||
|
"potato-lesion-nematode",
|
||||||
|
"potato-potato-rot-nematode",
|
||||||
|
"potato-root-knot-nematode",
|
||||||
|
"potato-sting-nematode",
|
||||||
|
"potato-stubby-root-nematode",
|
||||||
|
"potato-aerial-tubers",
|
||||||
|
"potato-air-pollution-injury",
|
||||||
|
"potato-black-heart",
|
||||||
|
"potato-blackspot-bruise",
|
||||||
|
"potato-elephant-hide",
|
||||||
|
"potato-hollow-heart",
|
||||||
|
"potato-internal-brown-spot-heat-necrosis",
|
||||||
|
"potato-jelly-end-rot",
|
||||||
|
"potato-physiological-leaf-roll",
|
||||||
|
"potato-psyllid-yellows",
|
||||||
|
"potato-shatter-bruise",
|
||||||
|
"potato-stem-end-browning",
|
||||||
|
"apple-dagger-nematode",
|
||||||
|
"apple-lesion-nematode",
|
||||||
|
"apple-pin-nematode",
|
||||||
|
"apple-ring-nematode",
|
||||||
|
"apple-root-knot-nematode",
|
||||||
|
"apple-bitter-pit",
|
||||||
|
"apple-blossom-blast",
|
||||||
|
"apple-fruit-cracking",
|
||||||
|
"apple-fruit-russet",
|
||||||
|
"apple-green-mottle",
|
||||||
|
"apple-hollow-apple",
|
||||||
|
"apple-internal-bark-necrosis-measles",
|
||||||
|
"apple-internal-browning",
|
||||||
|
"apple-jonathan-spot",
|
||||||
|
"apple-narrow-leaf",
|
||||||
|
"apple-necrotic-leaf-blotch-of-golden-delicious",
|
||||||
|
"apple-spray-injury",
|
||||||
|
"apple-storage-scald",
|
||||||
|
"apple-sunscald",
|
||||||
|
"apple-water-core",
|
||||||
|
"apricot-lesion",
|
||||||
|
"apricot-ring",
|
||||||
|
"apricot-root-knot",
|
||||||
|
"apricot-apricot-gumboil",
|
||||||
|
"avocado-algal-spot",
|
||||||
|
"avocado-blackstreak",
|
||||||
|
"avocado-littleleaf-rosette",
|
||||||
|
"avocado-tipburn",
|
||||||
|
"barley-cereal-cyst-nematode",
|
||||||
|
"barley-cereal-root-knot-nematode",
|
||||||
|
"barley-root-gall-nematode",
|
||||||
|
"barley-root-lesion-nematode",
|
||||||
|
"barley-stunt-nematode",
|
||||||
|
"barley-physiological-leaf-spot",
|
||||||
|
"carrot-cyst-nematode",
|
||||||
|
"carrot-dagger-nematode",
|
||||||
|
"carrot-lance-nematode",
|
||||||
|
"carrot-lesion-nematode",
|
||||||
|
"carrot-root-knot",
|
||||||
|
"carrot-sting-nematode",
|
||||||
|
"carrot-stubby-root-nematodes",
|
||||||
|
"carrot-crown-rot-disorder",
|
||||||
|
"carrot-heat-canker",
|
||||||
|
"carrot-hollow-black-heart",
|
||||||
|
"carrot-ozone-injury",
|
||||||
|
"carrot-root-scab",
|
||||||
|
"carrot-speckled-carrot",
|
||||||
|
"citrus-citrus-slump-nematode",
|
||||||
|
"citrus-dagger-nematode",
|
||||||
|
"citrus-lesion-nematode",
|
||||||
|
"citrus-needle-nematode",
|
||||||
|
"citrus-root-knot-nematode",
|
||||||
|
"citrus-sheath-nematode",
|
||||||
|
"citrus-slow-decline-citrus-nematode",
|
||||||
|
"citrus-spreading-decline-burrowing-nematode",
|
||||||
|
"citrus-sting-nematode",
|
||||||
|
"citrus-stubby-root-nematode",
|
||||||
|
"citrus-stunt-nematode",
|
||||||
|
"citrus-amachamiento",
|
||||||
|
"citrus-blossom-end-clearing",
|
||||||
|
"citrus-citrus-blight",
|
||||||
|
"citrus-creasing",
|
||||||
|
"citrus-crinkle-scurf",
|
||||||
|
"citrus-lemon-sieve-tube-necrosis",
|
||||||
|
"citrus-lime-blotch-wood-pocket",
|
||||||
|
"citrus-mesophyll-collapse",
|
||||||
|
"citrus-oleocellosis",
|
||||||
|
"citrus-postharvest-pitting",
|
||||||
|
"citrus-puffing",
|
||||||
|
"citrus-rind-breakdown",
|
||||||
|
"citrus-rind-staining",
|
||||||
|
"citrus-rind-stipple-of-grapefruit",
|
||||||
|
"citrus-rumple-of-lemon-fruit",
|
||||||
|
"citrus-shell-bark-complex",
|
||||||
|
"citrus-stem-end-rind-breakdown",
|
||||||
|
"citrus-stylar-end-breakdown-of-tahiti-lime",
|
||||||
|
"citrus-stylar-end-rind-breakdown",
|
||||||
|
"citrus-stylar-end-rot",
|
||||||
|
"citrus-tangerine-dieback",
|
||||||
|
"citrus-water-spot",
|
||||||
|
"citrus-zebra-skin",
|
||||||
|
"coconut-bristle-top",
|
||||||
|
"coconut-dry-bud-rot",
|
||||||
|
"coconut-finschafen-disease",
|
||||||
|
"coconut-frond-rot",
|
||||||
|
"coconut-leaf-scorch-decline",
|
||||||
|
"coconut-malaysia-wilt",
|
||||||
|
"coconut-red-ring-disease",
|
||||||
|
"coconut-porroca-disease",
|
||||||
|
"coconut-coconut-lethal-crown-atrophy",
|
||||||
|
"coffee-root-knot",
|
||||||
|
"coffee-",
|
||||||
|
"coffee-hot-and-cold-disease",
|
||||||
|
"coffee-physiological-effect-of-overbearing",
|
||||||
|
"corn-awl",
|
||||||
|
"corn-bulb-and-stem",
|
||||||
|
"corn-burrowing",
|
||||||
|
"corn-false-root-knot",
|
||||||
|
"corn-lance-columbia",
|
||||||
|
"corn-lesion",
|
||||||
|
"corn-needle",
|
||||||
|
"corn-ring",
|
||||||
|
"corn-root-knot",
|
||||||
|
"corn-sting",
|
||||||
|
"corn-stubby-root",
|
||||||
|
"cucumber-air-pollution-injury",
|
||||||
|
"cucumber-bitter-fruit",
|
||||||
|
"cucumber-blossom-end-rot",
|
||||||
|
"cucumber-bottle-neck-of-fruit",
|
||||||
|
"cucumber-sandburn",
|
||||||
|
"cucumber-sunscald-fruit",
|
||||||
|
"cucumber-windburn",
|
||||||
|
"cucumber-dagger-american",
|
||||||
|
"cucumber-lesion",
|
||||||
|
"cucumber-reniform",
|
||||||
|
"cucumber-ring",
|
||||||
|
"cucumber-root-knot",
|
||||||
|
"cucumber-sting",
|
||||||
|
"cucumber-stubby-root",
|
||||||
|
"grape-berry-rot",
|
||||||
|
"grape-black-measles",
|
||||||
|
"grape-esca-apoplexy",
|
||||||
|
"grape-little-leaf",
|
||||||
|
"grape-oxidant-stipple",
|
||||||
|
"grape-rupestris-speckle",
|
||||||
|
"grape-stem-necrosis-water-berry-grape-peduncle-necrosis",
|
||||||
|
"grape-",
|
||||||
|
"grape-dagger-american",
|
||||||
|
"grape-lesion",
|
||||||
|
"grape-needle",
|
||||||
|
"grape-reniform",
|
||||||
|
"grape-ring",
|
||||||
|
"grape-root-knot",
|
||||||
|
"grape-stubby-root",
|
||||||
|
"lettuce-brown-stain"
|
||||||
],
|
],
|
||||||
"totalFound": 4350
|
"totalFound": 5050
|
||||||
}
|
}
|
||||||
@@ -89,6 +89,11 @@ const CONTENT_TYPE_LABELS: Record<string, { emoji: string; title: string; descri
|
|||||||
description:
|
description:
|
||||||
"Disease symptom images that users have flagged as potentially incorrect or misleading.",
|
"Disease symptom images that users have flagged as potentially incorrect or misleading.",
|
||||||
},
|
},
|
||||||
|
disease_description: {
|
||||||
|
emoji: "📝",
|
||||||
|
title: "Disease Descriptions Flagged for Review",
|
||||||
|
description: "Disease descriptions that users have flagged as potentially inaccurate.",
|
||||||
|
},
|
||||||
disease_symptoms: {
|
disease_symptoms: {
|
||||||
emoji: "⚠️",
|
emoji: "⚠️",
|
||||||
title: "Disease Symptoms Flagged for Review",
|
title: "Disease Symptoms Flagged for Review",
|
||||||
@@ -245,6 +250,7 @@ async function main() {
|
|||||||
const orderedTypes = [
|
const orderedTypes = [
|
||||||
"plant_image",
|
"plant_image",
|
||||||
"disease_image",
|
"disease_image",
|
||||||
|
"disease_description",
|
||||||
"disease_symptoms",
|
"disease_symptoms",
|
||||||
"disease_causes",
|
"disease_causes",
|
||||||
"disease_treatment",
|
"disease_treatment",
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from "fs";
|
||||||
import { resolve, extname } from "path";
|
import { resolve, extname } from "path";
|
||||||
|
|
||||||
// Load .env.development for DB creds
|
// Load .env.development for DB creds
|
||||||
@@ -137,17 +137,32 @@ interface DuckDuckGoImageResult {
|
|||||||
width: number;
|
width: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SourceState {
|
||||||
|
exhausted: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
interface ClassProgress {
|
interface ClassProgress {
|
||||||
count: number;
|
count: number;
|
||||||
downloaded: number;
|
downloaded: number;
|
||||||
failed: number;
|
failed: number;
|
||||||
seenUrls: string[];
|
seenUrls: string[];
|
||||||
exhausted: boolean;
|
exhausted: boolean;
|
||||||
|
/** Per-source exhaustion tracking — prevents re-scraping exhausted sources on resume */
|
||||||
|
sources: {
|
||||||
|
db: SourceState;
|
||||||
|
duckduckgo: SourceState;
|
||||||
|
inaturalist: SourceState;
|
||||||
|
wikimedia: SourceState;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Progress {
|
interface Progress {
|
||||||
lastUpdated: string;
|
lastUpdated: string;
|
||||||
classes: Record<string, ClassProgress>;
|
classes: Record<string, ClassProgress>;
|
||||||
|
/** Phase checkpoint: 0=core, 1=full, 2=healthy. On resume, skip to this phase. */
|
||||||
|
phase: number;
|
||||||
|
/** Index within the current phase's disease array. On resume, skip to this index. */
|
||||||
|
phaseIndex: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── DB Loading ──────────────────────────────────────────────────────────────
|
// ─── DB Loading ──────────────────────────────────────────────────────────────
|
||||||
@@ -358,7 +373,7 @@ async function searchImagesCommons(
|
|||||||
srlimit: "50",
|
srlimit: "50",
|
||||||
sroffset: String(sroffset),
|
sroffset: String(sroffset),
|
||||||
format: "json",
|
format: "json",
|
||||||
origin: "*", // server-side API call
|
// No origin needed — server-side fetch, Wikimedia ignores CORS headers on API calls
|
||||||
});
|
});
|
||||||
|
|
||||||
const url = `https://commons.wikimedia.org/w/api.php?${params}`;
|
const url = `https://commons.wikimedia.org/w/api.php?${params}`;
|
||||||
@@ -472,9 +487,40 @@ async function downloadBatch(
|
|||||||
|
|
||||||
function loadProgress(): Progress {
|
function loadProgress(): Progress {
|
||||||
if (!existsSync(PROGRESS_FILE)) {
|
if (!existsSync(PROGRESS_FILE)) {
|
||||||
return { lastUpdated: new Date().toISOString(), classes: {} };
|
return {
|
||||||
|
lastUpdated: new Date().toISOString(),
|
||||||
|
classes: {},
|
||||||
|
phase: 0,
|
||||||
|
phaseIndex: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const raw = JSON.parse(readFileSync(PROGRESS_FILE, "utf-8")) as Partial<Progress>;
|
||||||
|
// Backward compat: ensure new fields exist
|
||||||
|
raw.phase ??= 0;
|
||||||
|
raw.phaseIndex ??= 0;
|
||||||
|
raw.classes ??= {};
|
||||||
|
// Ensure each class has the sources field
|
||||||
|
for (const key of Object.keys(raw.classes)) {
|
||||||
|
const cp = raw.classes[key] as Partial<ClassProgress>;
|
||||||
|
cp.sources ??= {
|
||||||
|
db: { exhausted: false },
|
||||||
|
duckduckgo: { exhausted: false },
|
||||||
|
inaturalist: { exhausted: false },
|
||||||
|
wikimedia: { exhausted: false },
|
||||||
|
};
|
||||||
|
cp.seenUrls ??= [];
|
||||||
|
}
|
||||||
|
return raw as Progress;
|
||||||
|
} catch {
|
||||||
|
console.warn(" ⚠ Corrupt progress file, starting fresh");
|
||||||
|
return {
|
||||||
|
lastUpdated: new Date().toISOString(),
|
||||||
|
classes: {},
|
||||||
|
phase: 0,
|
||||||
|
phaseIndex: 0,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return JSON.parse(readFileSync(PROGRESS_FILE, "utf-8")) as Progress;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveProgress(progress: Progress): void {
|
function saveProgress(progress: Progress): void {
|
||||||
@@ -490,6 +536,12 @@ function getClassProgress(progress: Progress, classId: string): ClassProgress {
|
|||||||
failed: 0,
|
failed: 0,
|
||||||
seenUrls: [],
|
seenUrls: [],
|
||||||
exhausted: false,
|
exhausted: false,
|
||||||
|
sources: {
|
||||||
|
db: { exhausted: false },
|
||||||
|
duckduckgo: { exhausted: false },
|
||||||
|
inaturalist: { exhausted: false },
|
||||||
|
wikimedia: { exhausted: false },
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return progress.classes[classId];
|
return progress.classes[classId];
|
||||||
@@ -514,6 +566,37 @@ function buildHealthyQueries(plant: string): string[] {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── File Reconciliation ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count actual image files in a class directory.
|
||||||
|
* Returns the count of files matching img_* pattern, OR 0 if dir doesn't exist.
|
||||||
|
*/
|
||||||
|
function countImagesInDir(classDir: string): number {
|
||||||
|
if (!existsSync(classDir)) return 0;
|
||||||
|
try {
|
||||||
|
const files = readdirSync(classDir);
|
||||||
|
return files.filter((f) => f.startsWith("img_")).length;
|
||||||
|
} catch {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reconcile a class's progress count with actual files on disk.
|
||||||
|
* If files were deleted after the progress file was saved, this
|
||||||
|
* adjusts the count downward so we re-download the missing ones.
|
||||||
|
* Returns the reconciled count.
|
||||||
|
*/
|
||||||
|
function reconcileClassCount(classDir: string, progressCount: number): number {
|
||||||
|
const fileCount = countImagesInDir(classDir);
|
||||||
|
if (fileCount < progressCount) {
|
||||||
|
console.log(` ↻ File count (${fileCount}) < progress count (${progressCount}) — reconciling`);
|
||||||
|
return fileCount;
|
||||||
|
}
|
||||||
|
return progressCount;
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Dataset Collection ─────────────────────────────────────────────────────
|
// ─── Dataset Collection ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
async function collectClassImages(
|
async function collectClassImages(
|
||||||
@@ -526,14 +609,32 @@ async function collectClassImages(
|
|||||||
fastMode = false, // Skip slow DuckDuckGo, use iNat + Commons only
|
fastMode = false, // Skip slow DuckDuckGo, use iNat + Commons only
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const cp = getClassProgress(progress, classId);
|
const cp = getClassProgress(progress, classId);
|
||||||
|
|
||||||
|
// ── Reconcile with actual files on disk ─────────────────────────────────
|
||||||
|
const actualCount = reconcileClassCount(classDir, cp.count);
|
||||||
|
if (actualCount !== cp.count) {
|
||||||
|
cp.count = actualCount;
|
||||||
|
saveProgress(progress);
|
||||||
|
}
|
||||||
|
|
||||||
const seenUrls = new Set(cp.seenUrls);
|
const seenUrls = new Set(cp.seenUrls);
|
||||||
|
const sources = cp.sources;
|
||||||
|
|
||||||
if (cp.count >= target) {
|
if (cp.count >= target) {
|
||||||
console.log(` ✓ Already have ${cp.count}/${target}`);
|
console.log(` ✓ Already have ${cp.count}/${target}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cp.exhausted) {
|
// Check if ALL sources are exhausted
|
||||||
|
const allExhausted =
|
||||||
|
sources.db.exhausted &&
|
||||||
|
sources.duckduckgo.exhausted &&
|
||||||
|
sources.inaturalist.exhausted &&
|
||||||
|
sources.wikimedia.exhausted;
|
||||||
|
|
||||||
|
if (allExhausted) {
|
||||||
|
cp.exhausted = true;
|
||||||
|
saveProgress(progress);
|
||||||
console.log(` ✓ Exhausted (${cp.count}/${target})`);
|
console.log(` ✓ Exhausted (${cp.count}/${target})`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -541,73 +642,111 @@ async function collectClassImages(
|
|||||||
mkdirSync(classDir, { recursive: true });
|
mkdirSync(classDir, { recursive: true });
|
||||||
|
|
||||||
const allUrls: string[] = [];
|
const allUrls: string[] = [];
|
||||||
let exhausted = false;
|
let anyNewResults = false;
|
||||||
|
const needed = target - cp.count;
|
||||||
|
|
||||||
// ── Source 0: Existing DB URLs ──────────────────────────────────────────
|
// ── Source 0: Existing DB URLs ──────────────────────────────────────────
|
||||||
const freshDbUrls = existingUrls.filter((u) => !seenUrls.has(u));
|
if (!sources.db.exhausted) {
|
||||||
if (freshDbUrls.length > 0) {
|
const freshDbUrls = existingUrls.filter((u) => !seenUrls.has(u));
|
||||||
console.log(` DB: ${freshDbUrls.length} existing URLs`);
|
if (freshDbUrls.length > 0) {
|
||||||
for (const url of freshDbUrls) {
|
console.log(` DB: ${freshDbUrls.length} existing URLs`);
|
||||||
if (allUrls.length >= target) break;
|
for (const url of freshDbUrls) {
|
||||||
seenUrls.add(url);
|
if (allUrls.length >= needed) break;
|
||||||
allUrls.push(url);
|
seenUrls.add(url);
|
||||||
|
allUrls.push(url);
|
||||||
|
}
|
||||||
|
if (freshDbUrls.length > 0) anyNewResults = true;
|
||||||
}
|
}
|
||||||
|
// DB source is always "exhausted" after processing its initial URLs
|
||||||
|
sources.db.exhausted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Source 1: DuckDuckGo ──────────────────────────────────────────────
|
// ── Source 1: DuckDuckGo ──────────────────────────────────────────────
|
||||||
// Skip DDG in fast mode (full set — DDG is slowest source)
|
if (!fastMode && !sources.duckduckgo.exhausted && allUrls.length < needed) {
|
||||||
if (!fastMode && allUrls.length < target) {
|
|
||||||
for (const query of queries) {
|
for (const query of queries) {
|
||||||
if (allUrls.length >= target) break;
|
if (allUrls.length >= needed) break;
|
||||||
process.stdout.write(` DDG: "${query.substring(0, 40)}"... `);
|
process.stdout.write(` DDG: "${query.substring(0, 40)}"... `);
|
||||||
const result = await collectImagesDuckDuckGo(query, target - allUrls.length, seenUrls);
|
const result = await collectImagesDuckDuckGo(query, needed - allUrls.length, seenUrls);
|
||||||
allUrls.push(...result.urls);
|
allUrls.push(...result.urls);
|
||||||
if (result.exhausted) exhausted = true;
|
if (result.exhausted) {
|
||||||
|
sources.duckduckgo.exhausted = true;
|
||||||
|
}
|
||||||
|
if (result.urls.length > 0) anyNewResults = true;
|
||||||
console.log(`${result.urls.length} new`);
|
console.log(`${result.urls.length} new`);
|
||||||
if (allUrls.length >= target) break;
|
if (allUrls.length >= needed) break;
|
||||||
|
}
|
||||||
|
// If DDG never gave us anything, mark exhausted to avoid re-trying
|
||||||
|
if (!anyNewResults && sources.duckduckgo.exhausted) {
|
||||||
|
/* already marked */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Source 2: iNaturalist ──────────────────────────────────────────────
|
// ── Source 2: iNaturalist ──────────────────────────────────────────────
|
||||||
if (allUrls.length < target) {
|
if (!sources.inaturalist.exhausted && allUrls.length < needed) {
|
||||||
const primaryQuery = queries[0];
|
const primaryQuery = queries[0];
|
||||||
console.log(` iNat: Searching...`);
|
console.log(` iNat: Searching...`);
|
||||||
const result = await searchImagesInaturalist(primaryQuery, target - allUrls.length, seenUrls);
|
const result = await searchImagesInaturalist(primaryQuery, needed - allUrls.length, seenUrls);
|
||||||
allUrls.push(...result.urls);
|
allUrls.push(...result.urls);
|
||||||
if (result.exhausted) exhausted = true;
|
if (result.exhausted) sources.inaturalist.exhausted = true;
|
||||||
|
if (result.urls.length > 0) anyNewResults = true;
|
||||||
console.log(` iNat: ${result.urls.length} images`);
|
console.log(` iNat: ${result.urls.length} images`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Source 3: Wikimedia Commons ────────────────────────────────────────
|
// ── Source 3: Wikimedia Commons ────────────────────────────────────────
|
||||||
if (allUrls.length < target) {
|
if (!sources.wikimedia.exhausted && allUrls.length < needed) {
|
||||||
const primaryQuery = queries[0];
|
const primaryQuery = queries[0];
|
||||||
console.log(` Commons: Searching...`);
|
console.log(` Commons: Searching...`);
|
||||||
const result = await searchImagesCommons(primaryQuery, target - allUrls.length, seenUrls);
|
const result = await searchImagesCommons(primaryQuery, needed - allUrls.length, seenUrls);
|
||||||
allUrls.push(...result.urls);
|
allUrls.push(...result.urls);
|
||||||
if (result.exhausted) exhausted = true;
|
if (result.exhausted) sources.wikimedia.exhausted = true;
|
||||||
|
if (result.urls.length > 0) anyNewResults = true;
|
||||||
console.log(` Commons: ${result.urls.length} images`);
|
console.log(` Commons: ${result.urls.length} images`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allUrls.length === 0) {
|
if (allUrls.length === 0) {
|
||||||
cp.exhausted = exhausted;
|
cp.exhausted = true;
|
||||||
saveProgress(progress);
|
saveProgress(progress);
|
||||||
console.log(` ✗ No images found`);
|
console.log(` ✗ No images found — exhausted`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!anyNewResults && allUrls.length > 0) {
|
||||||
|
// Only DB URLs survived — nothing more will come from searches
|
||||||
|
cp.exhausted = true;
|
||||||
|
saveProgress(progress);
|
||||||
|
}
|
||||||
|
|
||||||
// Save progress with seen URLs BEFORE downloading
|
// Save progress with seen URLs BEFORE downloading
|
||||||
cp.seenUrls = Array.from(seenUrls);
|
cp.seenUrls = Array.from(seenUrls);
|
||||||
cp.exhausted = exhausted;
|
|
||||||
saveProgress(progress);
|
saveProgress(progress);
|
||||||
|
|
||||||
console.log(` Downloading ${allUrls.length} images...`);
|
console.log(` Downloading ${allUrls.length} images...`);
|
||||||
|
|
||||||
const { downloaded, failed } = await downloadBatch(allUrls, classDir, cp.count);
|
// Use actual file count as start index so filenames don't have gaps
|
||||||
|
const startIndex = countImagesInDir(classDir);
|
||||||
|
const { downloaded, failed } = await downloadBatch(allUrls, classDir, startIndex);
|
||||||
|
|
||||||
cp.count += downloaded;
|
// Re-count actual files on disk after download (more reliable than tracking)
|
||||||
|
const newTotal = countImagesInDir(classDir);
|
||||||
|
cp.count = newTotal;
|
||||||
cp.downloaded += downloaded;
|
cp.downloaded += downloaded;
|
||||||
cp.failed += failed;
|
cp.failed += failed;
|
||||||
|
|
||||||
|
// Check if all sources exhausted
|
||||||
|
if (
|
||||||
|
sources.db.exhausted &&
|
||||||
|
sources.duckduckgo.exhausted &&
|
||||||
|
sources.inaturalist.exhausted &&
|
||||||
|
sources.wikimedia.exhausted
|
||||||
|
) {
|
||||||
|
cp.exhausted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't mark exhausted if we still have room to grow
|
||||||
|
if (cp.count >= target) {
|
||||||
|
cp.exhausted = true;
|
||||||
|
}
|
||||||
|
|
||||||
saveProgress(progress);
|
saveProgress(progress);
|
||||||
|
|
||||||
const pct = Math.round((cp.count / target) * 100);
|
const pct = Math.round((cp.count / target) * 100);
|
||||||
@@ -645,7 +784,12 @@ async function main() {
|
|||||||
console.log("PHASE 1: Core Diseases (100 images each)");
|
console.log("PHASE 1: Core Diseases (100 images each)");
|
||||||
console.log("─".repeat(60));
|
console.log("─".repeat(60));
|
||||||
|
|
||||||
for (let i = 0; i < coreDiseases.length; i++) {
|
const coreStart = progress.phase === 0 ? progress.phaseIndex : 0;
|
||||||
|
if (coreStart > 0) {
|
||||||
|
console.log(` Resuming from disease #${coreStart + 1} (${((coreStart / coreDiseases.length) * 100).toFixed(0)}% done)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = coreStart; i < coreDiseases.length; i++) {
|
||||||
const d = coreDiseases[i];
|
const d = coreDiseases[i];
|
||||||
const classDir = resolve(DATASET_DIR, d.id);
|
const classDir = resolve(DATASET_DIR, d.id);
|
||||||
const queries = buildSearchQueries(d);
|
const queries = buildSearchQueries(d);
|
||||||
@@ -655,6 +799,11 @@ async function main() {
|
|||||||
console.log(`\n[${i + 1}/${coreDiseases.length}] (${pct}%) ${d.name || d.id} (${d.plantId})`);
|
console.log(`\n[${i + 1}/${coreDiseases.length}] (${pct}%) ${d.name || d.id} (${d.plantId})`);
|
||||||
|
|
||||||
await collectClassImages(d.id, queries, TARGET_CORE, progress, classDir, existingUrls);
|
await collectClassImages(d.id, queries, TARGET_CORE, progress, classDir, existingUrls);
|
||||||
|
|
||||||
|
// Save checkpoint: phase 0, at index i
|
||||||
|
progress.phase = 0;
|
||||||
|
progress.phaseIndex = i + 1;
|
||||||
|
saveProgress(progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Phase 2: Full set ──────────────────────────────────────────────────
|
// ── Phase 2: Full set ──────────────────────────────────────────────────
|
||||||
@@ -663,7 +812,12 @@ async function main() {
|
|||||||
console.log("PHASE 2: Full Disease Set (10 images each)");
|
console.log("PHASE 2: Full Disease Set (10 images each)");
|
||||||
console.log("─".repeat(60));
|
console.log("─".repeat(60));
|
||||||
|
|
||||||
for (let i = 0; i < fullDiseases.length; i++) {
|
const fullStart = progress.phase === 1 ? progress.phaseIndex : 0;
|
||||||
|
if (fullStart > 0) {
|
||||||
|
console.log(` Resuming from disease #${fullStart + 1} (${((fullStart / fullDiseases.length) * 100).toFixed(0)}% done)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = fullStart; i < fullDiseases.length; i++) {
|
||||||
const d = fullDiseases[i];
|
const d = fullDiseases[i];
|
||||||
const classDir = resolve(DATASET_DIR, d.id);
|
const classDir = resolve(DATASET_DIR, d.id);
|
||||||
const queries = buildSearchQueries(d);
|
const queries = buildSearchQueries(d);
|
||||||
@@ -673,6 +827,11 @@ async function main() {
|
|||||||
console.log(`\n[${i + 1}/${fullDiseases.length}] (${pct}%) ${d.id} (${d.plantId})`);
|
console.log(`\n[${i + 1}/${fullDiseases.length}] (${pct}%) ${d.id} (${d.plantId})`);
|
||||||
|
|
||||||
await collectClassImages(d.id, queries, TARGET_FULL, progress, classDir, existingUrls, true);
|
await collectClassImages(d.id, queries, TARGET_FULL, progress, classDir, existingUrls, true);
|
||||||
|
|
||||||
|
// Save checkpoint: phase 1, at index i
|
||||||
|
progress.phase = 1;
|
||||||
|
progress.phaseIndex = i + 1;
|
||||||
|
saveProgress(progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Phase 3: Healthy class ──────────────────────────────────────────────
|
// ── Phase 3: Healthy class ──────────────────────────────────────────────
|
||||||
@@ -683,6 +842,14 @@ async function main() {
|
|||||||
|
|
||||||
const healthyDir = resolve(DATASET_DIR, HEALTHY_CLASS);
|
const healthyDir = resolve(DATASET_DIR, HEALTHY_CLASS);
|
||||||
const healthyCp = getClassProgress(progress, HEALTHY_CLASS);
|
const healthyCp = getClassProgress(progress, HEALTHY_CLASS);
|
||||||
|
|
||||||
|
// Reconcile healthy class with files on disk
|
||||||
|
const healthyActualCount = reconcileClassCount(healthyDir, healthyCp.count);
|
||||||
|
if (healthyActualCount !== healthyCp.count) {
|
||||||
|
healthyCp.count = healthyActualCount;
|
||||||
|
saveProgress(progress);
|
||||||
|
}
|
||||||
|
|
||||||
const healthySeen = new Set(healthyCp.seenUrls);
|
const healthySeen = new Set(healthyCp.seenUrls);
|
||||||
|
|
||||||
if (healthyCp.count >= TARGET_HEALTHY) {
|
if (healthyCp.count >= TARGET_HEALTHY) {
|
||||||
@@ -730,16 +897,23 @@ async function main() {
|
|||||||
saveProgress(progress);
|
saveProgress(progress);
|
||||||
|
|
||||||
console.log(`\n Downloading ${totalHealthyUrls.length} healthy images...`);
|
console.log(`\n Downloading ${totalHealthyUrls.length} healthy images...`);
|
||||||
|
const healthyStartIndex = countImagesInDir(healthyDir);
|
||||||
const { downloaded, failed } = await downloadBatch(
|
const { downloaded, failed } = await downloadBatch(
|
||||||
totalHealthyUrls,
|
totalHealthyUrls,
|
||||||
healthyDir,
|
healthyDir,
|
||||||
healthyCp.count,
|
healthyStartIndex,
|
||||||
);
|
);
|
||||||
|
|
||||||
healthyCp.count += downloaded;
|
// Re-count actual files on disk
|
||||||
|
const newHealthyTotal = countImagesInDir(healthyDir);
|
||||||
|
healthyCp.count = newHealthyTotal;
|
||||||
healthyCp.downloaded += downloaded;
|
healthyCp.downloaded += downloaded;
|
||||||
healthyCp.failed += failed;
|
healthyCp.failed += failed;
|
||||||
|
|
||||||
|
if (healthyCp.count >= TARGET_HEALTHY) {
|
||||||
|
healthyCp.exhausted = true;
|
||||||
|
}
|
||||||
|
|
||||||
const pct = Math.round((healthyCp.count / TARGET_HEALTHY) * 100);
|
const pct = Math.round((healthyCp.count / TARGET_HEALTHY) * 100);
|
||||||
console.log(
|
console.log(
|
||||||
` Got ${downloaded} images. Total: ${healthyCp.count}/${TARGET_HEALTHY} (${pct}%)`,
|
` Got ${downloaded} images. Total: ${healthyCp.count}/${TARGET_HEALTHY} (${pct}%)`,
|
||||||
@@ -753,6 +927,11 @@ async function main() {
|
|||||||
|
|
||||||
// ── Summary ────────────────────────────────────────────────────────────────
|
// ── Summary ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// Mark all phases complete
|
||||||
|
progress.phase = 3;
|
||||||
|
progress.phaseIndex = 0;
|
||||||
|
saveProgress(progress);
|
||||||
|
|
||||||
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
||||||
const mins = Math.floor(elapsed / 60);
|
const mins = Math.floor(elapsed / 60);
|
||||||
const hrs = Math.floor(mins / 60);
|
const hrs = Math.floor(mins / 60);
|
||||||
@@ -765,7 +944,7 @@ async function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log("\n" + "=".repeat(60));
|
console.log("\n" + "=".repeat(60));
|
||||||
console.log("COMPLETE");
|
console.log(" ✅ ALL PHASES COMPLETE");
|
||||||
console.log("=".repeat(60));
|
console.log("=".repeat(60));
|
||||||
console.log(` Time: ${hrs}h ${mins % 60}m`);
|
console.log(` Time: ${hrs}h ${mins % 60}m`);
|
||||||
console.log(` Downloaded: ${totalDownloaded} images`);
|
console.log(` Downloaded: ${totalDownloaded} images`);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { v4 as uuidv4 } from "uuid";
|
|||||||
const VALID_CONTENT_TYPES = [
|
const VALID_CONTENT_TYPES = [
|
||||||
"plant_image",
|
"plant_image",
|
||||||
"disease_image",
|
"disease_image",
|
||||||
|
"disease_description",
|
||||||
"disease_symptoms",
|
"disease_symptoms",
|
||||||
"disease_causes",
|
"disease_causes",
|
||||||
"disease_treatment",
|
"disease_treatment",
|
||||||
|
|||||||
@@ -140,9 +140,19 @@ function DiseaseCard({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-sm text-zinc-600 dark:text-zinc-300 leading-relaxed mb-4">
|
<div className="flex items-start justify-between gap-4 mb-4">
|
||||||
{disease.description}
|
<p className="text-sm text-zinc-600 dark:text-zinc-300 leading-relaxed">
|
||||||
</p>
|
{disease.description}
|
||||||
|
</p>
|
||||||
|
<FlagButton
|
||||||
|
contentType="disease_description"
|
||||||
|
contentId={disease.id}
|
||||||
|
fieldName="description"
|
||||||
|
label="description"
|
||||||
|
small
|
||||||
|
className="shrink-0 mt-0.5"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Details grid */}
|
{/* Details grid */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { notFound } from "next/navigation";
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { getPlantWithDiseases } from "@/lib/api/diseases-db";
|
import { getPlantWithDiseases } from "@/lib/api/diseases-db";
|
||||||
import { getPlantDescription } from "@/lib/display-helpers";
|
import { getPlantDescription } from "@/lib/display-helpers";
|
||||||
|
import BetaNotice from "@/components/BetaNotice";
|
||||||
import DiseaseCards from "./DiseaseCards";
|
import DiseaseCards from "./DiseaseCards";
|
||||||
import PlantViewTracker from "@/components/PlantViewTracker";
|
import PlantViewTracker from "@/components/PlantViewTracker";
|
||||||
import FlagPlantImage from "@/components/FlagPlantImage";
|
import FlagPlantImage from "@/components/FlagPlantImage";
|
||||||
@@ -83,6 +84,8 @@ export default async function PlantDetailPage({ params }: Props) {
|
|||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
<BetaNotice variant="card" className="mb-6" />
|
||||||
|
|
||||||
{/* Plant hero */}
|
{/* Plant hero */}
|
||||||
<div className="flex flex-col sm:flex-row sm:items-start gap-6 mb-10">
|
<div className="flex flex-col sm:flex-row sm:items-start gap-6 mb-10">
|
||||||
{/* Plant image */}
|
{/* Plant image */}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import React, { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
import { getBrowsePlants } from "@/lib/api/browse";
|
import { getBrowsePlants } from "@/lib/api/browse";
|
||||||
import BrowseContent from "./BrowseContent";
|
import BrowseContent from "./BrowseContent";
|
||||||
import { PlantCardSkeleton } from "@/components/LoadingSkeleton";
|
import { PlantCardSkeleton } from "@/components/LoadingSkeleton";
|
||||||
|
import BetaNotice from "@/components/BetaNotice";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Browse page — fetches plants with disease counts from the database
|
* Browse page — fetches plants with disease counts from the database
|
||||||
@@ -12,27 +13,30 @@ export default async function BrowsePage() {
|
|||||||
const allPlants = await getBrowsePlants();
|
const allPlants = await getBrowsePlants();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense
|
<>
|
||||||
fallback={
|
<BetaNotice variant="full-width" />
|
||||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-8 sm:py-12">
|
<Suspense
|
||||||
<div className="mb-8">
|
fallback={
|
||||||
<div className="h-9 w-48 animate-pulse rounded bg-zinc-200 dark:bg-zinc-700" />
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-8 sm:py-12">
|
||||||
<div className="mt-2 h-5 w-72 animate-pulse rounded bg-zinc-200 dark:bg-zinc-700" />
|
<div className="mb-8">
|
||||||
|
<div className="h-9 w-48 animate-pulse rounded bg-zinc-200 dark:bg-zinc-700" />
|
||||||
|
<div className="mt-2 h-5 w-72 animate-pulse rounded bg-zinc-200 dark:bg-zinc-700" />
|
||||||
|
</div>
|
||||||
|
<div className="mb-6 h-12 w-full animate-pulse rounded-xl bg-zinc-200 dark:bg-zinc-700" />
|
||||||
|
<div className="flex gap-2 mb-8">
|
||||||
|
{Array.from({ length: 5 }, (_, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className="h-9 w-24 animate-pulse rounded-full bg-zinc-200 dark:bg-zinc-700"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<PlantCardSkeleton count={8} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-6 h-12 w-full animate-pulse rounded-xl bg-zinc-200 dark:bg-zinc-700" />
|
}
|
||||||
<div className="flex gap-2 mb-8">
|
>
|
||||||
{Array.from({ length: 5 }, (_, i) => (
|
<BrowseContent allPlants={allPlants} />
|
||||||
<div
|
</Suspense>
|
||||||
key={i}
|
</>
|
||||||
className="h-9 w-24 animate-pulse rounded-full bg-zinc-200 dark:bg-zinc-700"
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<PlantCardSkeleton count={8} />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<BrowseContent allPlants={allPlants} />
|
|
||||||
</Suspense>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
44
apps/web/src/components/BetaNotice.tsx
Normal file
44
apps/web/src/components/BetaNotice.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* BetaNotice — a banner informing users that the site is in beta,
|
||||||
|
* community-driven, and most data isn't reviewed by humans yet.
|
||||||
|
* Encourages use of the Flag button to flag content for review.
|
||||||
|
*
|
||||||
|
* Two layout variants:
|
||||||
|
* - "full-width" (default): stretches edge-to-edge with an inner max-w wrapper
|
||||||
|
* - "card": rounded card with border, suitable for inside content containers
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default function BetaNotice({
|
||||||
|
variant = "full-width",
|
||||||
|
className = "",
|
||||||
|
}: {
|
||||||
|
variant?: "full-width" | "card";
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
const containerClasses =
|
||||||
|
variant === "card"
|
||||||
|
? `rounded-xl bg-warning-amber-50 dark:bg-warning-amber-950/60 border border-warning-amber-200 dark:border-warning-amber-800 ${className}`
|
||||||
|
: `bg-warning-amber-50 dark:bg-warning-amber-950/60 border-b border-warning-amber-200 dark:border-warning-amber-800 ${className}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={containerClasses}>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
variant === "card" ? "px-4 sm:px-6 py-3" : "mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-3"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<p className="text-xs sm:text-sm text-warning-amber-800 dark:text-warning-amber-200 text-center leading-relaxed">
|
||||||
|
<span className="font-semibold">🚧 Beta — Community Driven.</span> Most data here is not
|
||||||
|
reviewed by humans. Spot something wrong or it could be better? Use the{" "}
|
||||||
|
<span className="inline-flex items-center gap-1 font-medium whitespace-nowrap">
|
||||||
|
<svg className="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||||
|
<path d="M3.5 2.75a.75.75 0 00-1.5 0v14.5a.75.75 0 001.5 0v-4.392l1.657-.348a6.453 6.453 0 014.271.572 7.948 7.948 0 005.965.524l2.078-.64A.75.75 0 0018 12.25v-8.5a.75.75 0 00-.904-.734l-2.38.501a7.25 7.25 0 01-4.186-.363l-.502-.2a8.75 8.75 0 00-5.053-.439l-1.475.31V2.75z" />
|
||||||
|
</svg>
|
||||||
|
Flag
|
||||||
|
</span>{" "}
|
||||||
|
button on any image or description to flag it for review.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -132,9 +132,18 @@ export default function DiseaseCard({
|
|||||||
|
|
||||||
{/* Full description */}
|
{/* Full description */}
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-sm font-semibold text-zinc-900 dark:text-zinc-100 mb-1">
|
<div className="flex items-center justify-between mb-1">
|
||||||
Description
|
<h4 className="text-sm font-semibold text-zinc-900 dark:text-zinc-100">
|
||||||
</h4>
|
Description
|
||||||
|
</h4>
|
||||||
|
<FlagButton
|
||||||
|
contentType="disease_description"
|
||||||
|
contentId={disease.id}
|
||||||
|
fieldName="description"
|
||||||
|
label="description"
|
||||||
|
small
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<p className="text-sm leading-relaxed text-zinc-600 dark:text-zinc-400">
|
<p className="text-sm leading-relaxed text-zinc-600 dark:text-zinc-400">
|
||||||
{disease.description}
|
{disease.description}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useState, useCallback } from "react";
|
|||||||
export type FlagContentType =
|
export type FlagContentType =
|
||||||
| "plant_image"
|
| "plant_image"
|
||||||
| "disease_image"
|
| "disease_image"
|
||||||
|
| "disease_description"
|
||||||
| "disease_symptoms"
|
| "disease_symptoms"
|
||||||
| "disease_causes"
|
| "disease_causes"
|
||||||
| "disease_treatment"
|
| "disease_treatment"
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import FlagButton from "@/components/FlagButton";
|
import FlagButton from "@/components/FlagButton";
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ export const flaggedContent = sqliteTable(
|
|||||||
enum: [
|
enum: [
|
||||||
"plant_image",
|
"plant_image",
|
||||||
"disease_image",
|
"disease_image",
|
||||||
|
"disease_description",
|
||||||
"disease_symptoms",
|
"disease_symptoms",
|
||||||
"disease_causes",
|
"disease_causes",
|
||||||
"disease_treatment",
|
"disease_treatment",
|
||||||
|
|||||||
Reference in New Issue
Block a user