import { useState, useEffect, useCallback, forwardRef, useImperativeHandle, } from "react"; import { useTranslation } from "react-i18next"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Save, Loader2, X, FolderInput, RotateCcw, ChevronsUpDown, } from "lucide-react"; import { cn } from "@/lib/utils"; import { toast } from "sonner"; import type { OmoGlobalConfig } from "@/types/omo"; import { OMO_DISABLEABLE_AGENTS, OMO_DISABLEABLE_MCPS, OMO_DISABLEABLE_HOOKS, OMO_DISABLEABLE_SKILLS, OMO_DEFAULT_SCHEMA_URL, OMO_SISYPHUS_AGENT_PLACEHOLDER, OMO_LSP_PLACEHOLDER, OMO_EXPERIMENTAL_PLACEHOLDER, OMO_BACKGROUND_TASK_PLACEHOLDER, OMO_BROWSER_AUTOMATION_PLACEHOLDER, OMO_CLAUDE_CODE_PLACEHOLDER, OMO_SLIM_DISABLEABLE_AGENTS, OMO_SLIM_DISABLEABLE_MCPS, OMO_SLIM_DISABLEABLE_HOOKS, OMO_SLIM_DEFAULT_SCHEMA_URL, } from "@/types/omo"; import { useOmoGlobalConfig, useSaveOmoGlobalConfig, useReadOmoLocalFile, useOmoSlimGlobalConfig, useSaveOmoSlimGlobalConfig, useReadOmoSlimLocalFile, } from "@/lib/query/omo"; interface PresetOption { readonly value: string; readonly label: string; } export interface OmoGlobalConfigFieldsRef { buildCurrentConfig: () => OmoGlobalConfig; buildCurrentConfigStrict: () => OmoGlobalConfig; importFromLocal: () => Promise; } interface OmoGlobalConfigFieldsProps { onStateChange?: (config: OmoGlobalConfig) => void; hideSaveButtons?: boolean; isSlim?: boolean; } type OmoAdvancedFieldKey = | "lspStr" | "experimentalStr" | "backgroundTaskStr" | "browserStr" | "claudeCodeStr"; const OMO_ADVANCED_JSON_FIELDS: ReadonlyArray<{ key: OmoAdvancedFieldKey; labelKey: string; defaultLabel: string; placeholder: string; minHeight: string; }> = [ { key: "lspStr", labelKey: "omo.advancedLsp", defaultLabel: "LSP Config", placeholder: OMO_LSP_PLACEHOLDER, minHeight: "200px", }, { key: "experimentalStr", labelKey: "omo.advancedExperimental", defaultLabel: "Experimental Features", placeholder: OMO_EXPERIMENTAL_PLACEHOLDER, minHeight: "120px", }, { key: "backgroundTaskStr", labelKey: "omo.advancedBackgroundTask", defaultLabel: "Background Tasks", placeholder: OMO_BACKGROUND_TASK_PLACEHOLDER, minHeight: "250px", }, { key: "browserStr", labelKey: "omo.advancedBrowserAutomation", defaultLabel: "Browser Automation", placeholder: OMO_BROWSER_AUTOMATION_PLACEHOLDER, minHeight: "80px", }, { key: "claudeCodeStr", labelKey: "omo.advancedClaudeCode", defaultLabel: "Claude Code", placeholder: OMO_CLAUDE_CODE_PLACEHOLDER, minHeight: "180px", }, ]; const OMO_SLIM_ADVANCED_KEYS: ReadonlySet = new Set([ "lspStr", "experimentalStr", ]); function TagListEditor({ label, values, onChange, placeholder, presets, }: { label: string; values: string[]; onChange: (values: string[]) => void; placeholder?: string; presets?: readonly PresetOption[]; }) { const { t } = useTranslation(); const [open, setOpen] = useState(false); const [search, setSearch] = useState(""); const toggleValue = (v: string) => { if (values.includes(v)) { onChange(values.filter((x) => x !== v)); } else { onChange([...values, v]); } }; const customValue = search.trim(); const canAddCustom = customValue.length > 0 && !values.includes(customValue); const triggerText = values.length === 0 ? placeholder || t("omo.selectPlaceholder", { defaultValue: "Select..." }) : values.length === 1 ? values[0] : `${values[0]} +${values.length - 1}`; const availablePresets = presets?.filter( (p) => !search.trim() || p.label.toLowerCase().includes(search.toLowerCase()) || p.value.toLowerCase().includes(search.toLowerCase()), ); return (
{values.length > 0 && ( )}
{values.length > 0 && (
{values.map((v, i) => ( {v} ))}
)}
setSearch(e.target.value)} onKeyDown={(e) => { e.stopPropagation(); if (e.key === "Enter" && canAddCustom) { e.preventDefault(); onChange([...values, customValue]); setSearch(""); } }} placeholder={ placeholder || t("omo.searchOrType", { defaultValue: "Search or type custom value...", }) } className="h-7 text-sm" autoFocus />
{canAddCustom && ( )}
{availablePresets && availablePresets.length > 0 ? ( availablePresets.map((p) => { const checked = values.includes(p.value); return ( e.preventDefault()} onCheckedChange={() => toggleValue(p.value)} className="text-sm" > {p.label} ); }) ) : (
{t("omo.noMatches", { defaultValue: "No matches" })}
)}
); } function JsonTextareaField({ label, value, onChange, placeholder, minHeight, }: { label: string; value: string; onChange: (value: string) => void; placeholder?: string; minHeight?: string; }) { return (