From 8afe1e6803dd6f5b5e3ed34fa984a4be3a09abee Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Sun, 8 Jun 2025 17:29:40 -0700 Subject: [PATCH] feat: Update chat UI to be Open SWE specific (#76) (#91) * init repo-selector * add branch selector UI, extend GH hook w branch info * add reconnectOnMount: true resumeStream, remove repo description from selector, fix styling * styling repo + branch * udpate getBranch calls to include targetRepo arg * format * add search to repo + branch selectors * add settings icon link to /github for dev convenience * add default branch selection * format * minor cleanup * init task list UI demo * individual task component * tasks real data * task visual improvement * persisting tasks, navigate to thread onClick * task UI * TaskSidebar, Navigate to tasks * org tasks by thread * group by thread * improve sidebar, task-list, rm github modal on refresh * collapse sidebar works when viewing a thread * remove micro-tasks expansion from threads component, change taskId to taskIndex for persistence + simplicity * init config sidebar ui * open and close config sidebar * add ref for getAllTasks + add graph_id metadata * format * create threadItem component * format * add threadUtils * centralize + refactor types * format * improve thread status, realtime updates * fix scroll issue * remove temperature from config-sidebar * Change model via sidebar * fix flashing on updates * init plan UI * centralize types * self CR: refactoring, cleanup, remove comments, add todos * CR: clean comments + logs, fix repo selector bug * fix: Update example env file in web (#77) * disable auto scroll on pageload * reposelector: use default branch not main master * remove archived tab temp, default branch listed first in branchSelector dropdown * auto select first repo * improve status indicator * refactor status indicator * simplify status implementation to use Langgraph * util moved from provider to thread-utils * linting, improve task completion logic * feat: Support followup requests (#60) * feat: Support followup requests * cr * cr * fix * account for pr already exists * remove task provider * fix: Better prompting and context provided for codebase structure awareness (#85) * fix: Better prompting and context provided for codebase structure awareness * cr * linting + skeleton loader * fix: branch selector, add github api pagination * small branch update * update task completion status realtime * threadItem stream remove flashing/polling * feat: Create a shared package (#89) * feat: Create a shared package * formatting * fix: drop passing target repo to getBranchName * drop docs * use shared package for config fields in config sidebar * fix: styling and structure for repo/branch selectors * fix: use shared types/objects for config fields * default key for configs * fix: improved styling for threads * fix renering num tasks * disable repo branch selector when chat started * cr * cr * bump deps * fix: rendering plan in interrupt * cr * cr --------- Co-authored-by: Dylan Boudro <121908331+starmorph@users.noreply.github.com> --- README.md | 10 + apps/open-swe/package.json | 5 +- apps/open-swe/src/nodes/diagnose-error.ts | 6 +- .../open-swe/src/nodes/generate-conclusion.ts | 6 +- apps/open-swe/src/nodes/generate-message.ts | 2 +- apps/open-swe/src/nodes/interrupt-plan.ts | 12 +- .../src/nodes/summarize-task-steps.ts | 6 +- .../planner/nodes/generate-message.ts | 2 +- apps/open-swe/src/utils/git.ts | 2 +- apps/open-swe/src/utils/message/content.ts | 11 +- apps/open-swe/src/utils/user-request.ts | 2 +- apps/web/package.json | 15 +- apps/web/public/favicon.png | Bin 0 -> 2610 bytes .../api/github/installation-callback/route.ts | 1 - .../api/github/installation/callback/route.ts | 1 - apps/web/src/app/github/page.tsx | 4 +- apps/web/src/app/layout.tsx | 5 + .../configuration-sidebar/config-field.tsx | 291 +++++++++++++++ .../configuration-sidebar/config-section.tsx | 28 ++ .../configuration-sidebar/index.tsx | 153 ++++++++ ...nProvider.tsx => agent-token-provider.tsx} | 4 +- .../{ => github}/github-oauth-button.tsx | 16 +- ...llAppButton.tsx => install-app-button.tsx} | 0 .../repo-branch-selectors/branch-selector.tsx | 240 +++++++++++++ .../github/repo-branch-selectors/index.tsx | 31 ++ .../repository-selector.tsx | 218 ++++++++++++ ...RepositoryList.tsx => repository-list.tsx} | 2 +- apps/web/src/components/icons/github.tsx | 13 +- apps/web/src/components/plan/index.ts | 1 + apps/web/src/components/plan/plan-viewer.tsx | 128 +++++++ .../src/components/sidebar-buttons/index.tsx | 114 ++++++ apps/web/src/components/status-indicator.tsx | 30 ++ apps/web/src/components/task-list-sidebar.tsx | 127 +++++++ apps/web/src/components/task-list.tsx | 102 ++++++ apps/web/src/components/thread-item.tsx | 134 +++++++ .../components/thread/MultimodalPreview.tsx | 2 +- .../components/inbox-item-input.tsx | 168 +++++++-- .../agent-inbox/components/thread-id.tsx | 2 +- .../components/tool-call-table.tsx | 2 +- .../hooks/use-interrupted-actions.tsx | 8 + .../components/thread/agent-inbox/utils.ts | 4 +- .../src/components/thread/history/index.tsx | 11 +- apps/web/src/components/thread/index.tsx | 209 +++++++---- .../src/components/thread/markdown-text.tsx | 2 +- .../src/components/thread/messages/human.tsx | 8 +- .../src/components/thread/messages/shared.tsx | 2 +- .../components/thread/tooltip-icon-button.tsx | 44 --- apps/web/src/components/ui/alert-dialog.tsx | 166 +++++++++ apps/web/src/components/ui/alert.tsx | 66 ++++ apps/web/src/components/ui/badge.tsx | 46 +++ apps/web/src/components/ui/collapsible.tsx | 36 ++ apps/web/src/components/ui/scroll-area.tsx | 56 +++ apps/web/src/components/ui/slider.tsx | 63 ++++ apps/web/src/components/ui/tabs.tsx | 66 ++++ .../src/components/ui/tooltip-icon-button.tsx | 74 ++++ apps/web/src/hooks/use-config-store.tsx | 106 ++++++ apps/web/src/hooks/use-file-upload.tsx | 9 +- apps/web/src/hooks/useGitHubApp.ts | 171 ++++++++- apps/web/src/lib/api-key.tsx | 10 - apps/web/src/lib/plan-utils.ts | 56 +++ apps/web/src/providers/Stream.tsx | 126 +++++-- apps/web/src/providers/Thread.tsx | 236 ++++++++++++- apps/web/src/providers/client.ts | 3 +- apps/web/src/types/file-upload.ts | 7 + apps/web/src/types/index.ts | 50 +++ apps/web/src/utils/github.ts | 91 +++++ langgraph.json | 2 +- package.json | 3 + packages/shared/package.json | 3 +- packages/shared/src/configurable-metadata.ts | 69 ++++ packages/shared/src/constants.ts | 2 + packages/shared/src/messages.ts | 10 + packages/shared/src/open-swe/models.ts | 63 ++++ packages/shared/src/open-swe/types.ts | 332 ++++++++---------- yarn.lock | 230 ++++++++++-- 75 files changed, 3857 insertions(+), 479 deletions(-) create mode 100644 apps/web/public/favicon.png create mode 100644 apps/web/src/components/configuration-sidebar/config-field.tsx create mode 100644 apps/web/src/components/configuration-sidebar/config-section.tsx create mode 100644 apps/web/src/components/configuration-sidebar/index.tsx rename apps/web/src/components/github/{AgentTokenProvider.tsx => agent-token-provider.tsx} (98%) rename apps/web/src/components/{ => github}/github-oauth-button.tsx (91%) rename apps/web/src/components/github/{InstallAppButton.tsx => install-app-button.tsx} (100%) create mode 100644 apps/web/src/components/github/repo-branch-selectors/branch-selector.tsx create mode 100644 apps/web/src/components/github/repo-branch-selectors/index.tsx create mode 100644 apps/web/src/components/github/repo-branch-selectors/repository-selector.tsx rename apps/web/src/components/github/{RepositoryList.tsx => repository-list.tsx} (98%) create mode 100644 apps/web/src/components/plan/index.ts create mode 100644 apps/web/src/components/plan/plan-viewer.tsx create mode 100644 apps/web/src/components/sidebar-buttons/index.tsx create mode 100644 apps/web/src/components/status-indicator.tsx create mode 100644 apps/web/src/components/task-list-sidebar.tsx create mode 100644 apps/web/src/components/task-list.tsx create mode 100644 apps/web/src/components/thread-item.tsx delete mode 100644 apps/web/src/components/thread/tooltip-icon-button.tsx create mode 100644 apps/web/src/components/ui/alert-dialog.tsx create mode 100644 apps/web/src/components/ui/alert.tsx create mode 100644 apps/web/src/components/ui/badge.tsx create mode 100644 apps/web/src/components/ui/collapsible.tsx create mode 100644 apps/web/src/components/ui/scroll-area.tsx create mode 100644 apps/web/src/components/ui/slider.tsx create mode 100644 apps/web/src/components/ui/tabs.tsx create mode 100644 apps/web/src/components/ui/tooltip-icon-button.tsx create mode 100644 apps/web/src/hooks/use-config-store.tsx delete mode 100644 apps/web/src/lib/api-key.tsx create mode 100644 apps/web/src/lib/plan-utils.ts create mode 100644 apps/web/src/types/file-upload.ts create mode 100644 apps/web/src/types/index.ts create mode 100644 packages/shared/src/configurable-metadata.ts create mode 100644 packages/shared/src/messages.ts create mode 100644 packages/shared/src/open-swe/models.ts diff --git a/README.md b/README.md index d2e02fc9..9bb64eee 100644 --- a/README.md +++ b/README.md @@ -138,3 +138,13 @@ Once you've accepted the plan, it will begin the execution flow. When the agent Open SWE will automatically create a branch whenever you create a new thread with a naming format of `open-swe/`. Every time a file is created, modified, or deleted, the changes will be committed to this branch. You can access the changes in the repository by checking out this branch. + + +## Install Daytona CLI +```bash +# Mac os +brew install daytonaio/cli/daytona +# Windows +powershell -Command "irm https://get.daytona.io/windows | iex" + +``` \ No newline at end of file diff --git a/apps/open-swe/package.json b/apps/open-swe/package.json index 2e591258..d689237b 100644 --- a/apps/open-swe/package.json +++ b/apps/open-swe/package.json @@ -10,6 +10,7 @@ "type": "module", "scripts": { "dev": "langgraphjs dev --no-browser --config ../../langgraph.json", + "clean": "rm -rf ../../.langgraph_api || true && rm -rf .turbo || true && rm -rf ./dist || true", "build": "tsc", "lint": "eslint .", "lint:fix": "eslint . --fix", @@ -24,8 +25,8 @@ "@langchain/anthropic": "^0.3.20", "@langchain/core": "^0.3.56", "@langchain/google-genai": "^0.2.9", - "@langchain/langgraph": "^0.2.73", - "@langchain/langgraph-sdk": "^0.0.78", + "@langchain/langgraph": "^0.3.1", + "@langchain/langgraph-sdk": "^0.0.83", "@langchain/openai": "^0.5.10", "@octokit/rest": "^22.0.0", "@open-swe/shared": "workspace:*", diff --git a/apps/open-swe/src/nodes/diagnose-error.ts b/apps/open-swe/src/nodes/diagnose-error.ts index 6a723fad..9cd7f451 100644 --- a/apps/open-swe/src/nodes/diagnose-error.ts +++ b/apps/open-swe/src/nodes/diagnose-error.ts @@ -10,10 +10,8 @@ import { PlanItem, } from "@open-swe/shared/open-swe/types"; import { formatPlanPromptWithSummaries } from "../utils/plan-prompt.js"; -import { - getMessageContentString, - getMessageString, -} from "../utils/message/content.js"; +import { getMessageString } from "../utils/message/content.js"; +import { getMessageContentString } from "@open-swe/shared/messages"; import { loadModel, Task } from "../utils/load-model.js"; import { z } from "zod"; import { createLogger, LogLevel } from "../utils/logger.js"; diff --git a/apps/open-swe/src/nodes/generate-conclusion.ts b/apps/open-swe/src/nodes/generate-conclusion.ts index dbd1394f..1d6acc71 100644 --- a/apps/open-swe/src/nodes/generate-conclusion.ts +++ b/apps/open-swe/src/nodes/generate-conclusion.ts @@ -5,10 +5,8 @@ import { PlanItem, } from "@open-swe/shared/open-swe/types"; import { loadModel, Task } from "../utils/load-model.js"; -import { - getMessageContentString, - getMessageString, -} from "../utils/message/content.js"; +import { getMessageContentString } from "@open-swe/shared/messages"; +import { getMessageString } from "../utils/message/content.js"; import { createLogger, LogLevel } from "../utils/logger.js"; import { getUserRequest } from "../utils/user-request.js"; import { diff --git a/apps/open-swe/src/nodes/generate-message.ts b/apps/open-swe/src/nodes/generate-message.ts index 4f7f3afa..1562af45 100644 --- a/apps/open-swe/src/nodes/generate-message.ts +++ b/apps/open-swe/src/nodes/generate-message.ts @@ -14,7 +14,7 @@ import { formatPlanPrompt } from "../utils/plan-prompt.js"; import { stopSandbox } from "../utils/sandbox.js"; import { createLogger, LogLevel } from "../utils/logger.js"; import { getCurrentPlanItem } from "../utils/current-task.js"; -import { getMessageContentString } from "../utils/message/content.js"; +import { getMessageContentString } from "@open-swe/shared/messages"; import { getActivePlanItems } from "../utils/task-plan.js"; import { SANDBOX_ROOT_DIR } from "@open-swe/shared/constants"; diff --git a/apps/open-swe/src/nodes/interrupt-plan.ts b/apps/open-swe/src/nodes/interrupt-plan.ts index 44169f3a..2b9327f2 100644 --- a/apps/open-swe/src/nodes/interrupt-plan.ts +++ b/apps/open-swe/src/nodes/interrupt-plan.ts @@ -8,6 +8,10 @@ import { import { startSandbox } from "../utils/sandbox.js"; import { createNewTask } from "../utils/task-plan.js"; import { getUserRequest } from "../utils/user-request.js"; +import { + PLAN_INTERRUPT_ACTION_TITLE, + PLAN_INTERRUPT_DELIMITER, +} from "@open-swe/shared/constants"; export async function interruptPlan(state: GraphState): Promise { const { proposedPlan } = state; @@ -17,9 +21,9 @@ export async function interruptPlan(state: GraphState): Promise { const interruptRes = interrupt({ action_request: { - action: "Approve/Edit Plan", + action: PLAN_INTERRUPT_ACTION_TITLE, args: { - plan: proposedPlan.join("\n:::\n"), + plan: proposedPlan.join(`\n${PLAN_INTERRUPT_DELIMITER}\n`), }, }, config: { @@ -29,7 +33,7 @@ export async function interruptPlan(state: GraphState): Promise { allow_ignore: true, }, description: `A new plan has been generated for your request. Please review it and either approve it, edit it, respond to it, or ignore it. Responses will be passed to an LLM where it will rewrite then plan. - If editing the plan, ensure each step in the plan is separated by ":::".`, + If editing the plan, ensure each step in the plan is separated by "${PLAN_INTERRUPT_DELIMITER}".`, })[0]; if (!state.sandboxSessionId) { @@ -66,7 +70,7 @@ export async function interruptPlan(state: GraphState): Promise { // Plan was edited, route to the generate-action node to start taking actions. const editedPlan = (interruptRes.args as ActionRequest).args.plan - .split(":::") + .split(PLAN_INTERRUPT_DELIMITER) .map((step: string) => step.trim()); const planItems = editedPlan.map((p: string, index: number) => ({ diff --git a/apps/open-swe/src/nodes/summarize-task-steps.ts b/apps/open-swe/src/nodes/summarize-task-steps.ts index d934277d..9f8fc8e3 100644 --- a/apps/open-swe/src/nodes/summarize-task-steps.ts +++ b/apps/open-swe/src/nodes/summarize-task-steps.ts @@ -9,10 +9,8 @@ import { loadModel, Task } from "../utils/load-model.js"; import { AIMessage, BaseMessage } from "@langchain/core/messages"; import { formatPlanPrompt } from "../utils/plan-prompt.js"; import { createLogger, LogLevel } from "../utils/logger.js"; -import { - getMessageContentString, - getMessageString, -} from "../utils/message/content.js"; +import { getMessageContentString } from "@open-swe/shared/messages"; +import { getMessageString } from "../utils/message/content.js"; import { removeLastTaskMessages } from "../utils/message/modify-array.js"; import { Command } from "@langchain/langgraph"; import { ConfigurableModel } from "langchain/chat_models/universal"; diff --git a/apps/open-swe/src/subgraphs/planner/nodes/generate-message.ts b/apps/open-swe/src/subgraphs/planner/nodes/generate-message.ts index 1e66ae00..9e1ad72f 100644 --- a/apps/open-swe/src/subgraphs/planner/nodes/generate-message.ts +++ b/apps/open-swe/src/subgraphs/planner/nodes/generate-message.ts @@ -3,7 +3,7 @@ import { shellTool } from "../../../tools/index.js"; import { PlannerGraphState, PlannerGraphUpdate } from "../types.js"; import { GraphConfig } from "@open-swe/shared/open-swe/types"; import { createLogger, LogLevel } from "../../../utils/logger.js"; -import { getMessageContentString } from "../../../utils/message/content.js"; +import { getMessageContentString } from "@open-swe/shared/messages"; import { getUserRequest } from "../../../utils/user-request.js"; import { isHumanMessage } from "@langchain/core/messages"; import { formatFollowupMessagePrompt } from "../utils/followup-prompt.js"; diff --git a/apps/open-swe/src/utils/git.ts b/apps/open-swe/src/utils/git.ts index ac375213..9311153a 100644 --- a/apps/open-swe/src/utils/git.ts +++ b/apps/open-swe/src/utils/git.ts @@ -306,7 +306,7 @@ export async function configureGitUserInRepo( } // Set user email - use fetched email or fallback to a generic noreply address - const emailToUse = userEmail || "open-swe-bot@noreply.github.com"; + const emailToUse = userEmail || `${repo}-bot@noreply.github.com`; const configUserEmailOutput = await sandbox.process.executeCommand( `git config user.email "${emailToUse}"`, absoluteRepoDir, diff --git a/apps/open-swe/src/utils/message/content.ts b/apps/open-swe/src/utils/message/content.ts index e4ef6159..65c1b1c9 100644 --- a/apps/open-swe/src/utils/message/content.ts +++ b/apps/open-swe/src/utils/message/content.ts @@ -6,20 +6,11 @@ import { isHumanMessage, isSystemMessage, isToolMessage, - MessageContent, SystemMessage, ToolMessage, } from "@langchain/core/messages"; import { ToolCall } from "@langchain/core/messages/tool"; - -export function getMessageContentString(content: MessageContent): string { - if (typeof content === "string") return content; - - return content - .filter((c): c is { type: "text"; text: string } => c.type === "text") - .map((c) => c.text) - .join(" "); -} +import { getMessageContentString } from "@open-swe/shared/messages"; export function getToolCallsString(toolCalls: ToolCall[] | undefined): string { if (!toolCalls?.length) return ""; diff --git a/apps/open-swe/src/utils/user-request.ts b/apps/open-swe/src/utils/user-request.ts index 4ee786a5..23c3a079 100644 --- a/apps/open-swe/src/utils/user-request.ts +++ b/apps/open-swe/src/utils/user-request.ts @@ -3,7 +3,7 @@ import { isHumanMessage, HumanMessage, } from "@langchain/core/messages"; -import { getMessageContentString } from "./message/content.js"; +import { getMessageContentString } from "@open-swe/shared/messages"; export function getUserRequest( messages: BaseMessage[], diff --git a/apps/web/package.json b/apps/web/package.json index ffb8824d..5dc3280d 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -20,16 +20,22 @@ }, "dependencies": { "@langchain/core": "^0.3.57", - "@langchain/langgraph": "^0.2.73", - "@langchain/langgraph-sdk": "^0.0.78", + "@langchain/langgraph": "^0.3.1", + "@langchain/langgraph-sdk": "^0.0.83", + "@open-swe/shared": "workspace:*", + "@radix-ui/react-alert-dialog": "^1.1.14", "@radix-ui/react-avatar": "^1.1.3", + "@radix-ui/react-collapsible": "^1.1.11", "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-label": "^2.1.2", "@radix-ui/react-popover": "^1.1.14", + "@radix-ui/react-scroll-area": "^1.2.9", "@radix-ui/react-select": "^2.2.5", "@radix-ui/react-separator": "^1.1.2", - "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-slider": "^1.3.5", + "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "^1.1.3", + "@radix-ui/react-tabs": "^1.1.12", "@radix-ui/react-tooltip": "^1.1.8", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -58,7 +64,8 @@ "tailwindcss-animate": "^1.0.7", "use-stick-to-bottom": "^1.0.46", "uuid": "^11.1.0", - "zod": "^3.24.2" + "zod": "^3.24.2", + "zustand": "^5.0.5" }, "devDependencies": { "@eslint/js": "^9.19.0", diff --git a/apps/web/public/favicon.png b/apps/web/public/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..38454444bdce43df7d5f16a3f90b70f8db095913 GIT binary patch literal 2610 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 literal 0 HcmV?d00001 diff --git a/apps/web/src/app/api/github/installation-callback/route.ts b/apps/web/src/app/api/github/installation-callback/route.ts index 5ed0ed92..234a48cf 100644 --- a/apps/web/src/app/api/github/installation-callback/route.ts +++ b/apps/web/src/app/api/github/installation-callback/route.ts @@ -13,7 +13,6 @@ export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url); const installationId = searchParams.get("installation_id"); - const setupAction = searchParams.get("setup_action"); // Get the return URL from cookies const returnTo = diff --git a/apps/web/src/app/api/github/installation/callback/route.ts b/apps/web/src/app/api/github/installation/callback/route.ts index 63d66023..30045f65 100644 --- a/apps/web/src/app/api/github/installation/callback/route.ts +++ b/apps/web/src/app/api/github/installation/callback/route.ts @@ -9,7 +9,6 @@ export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url); const installationId = searchParams.get("installation_id"); - const setupAction = searchParams.get("setup_action"); if (!installationId) { return NextResponse.redirect( diff --git a/apps/web/src/app/github/page.tsx b/apps/web/src/app/github/page.tsx index b28a5f17..c033a186 100644 --- a/apps/web/src/app/github/page.tsx +++ b/apps/web/src/app/github/page.tsx @@ -25,7 +25,7 @@ export default function GitHubPage() { setError(errorData.error); } } - } catch (err) { + } catch { setError("Failed to check installation status"); } }; @@ -50,7 +50,7 @@ export default function GitHubPage() { const errorData = await response.json(); setError(errorData.error); } - } catch (err) { + } catch { setError("Failed to fetch repositories"); } finally { setLoading(false); diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 37d29c74..a8ff7e4c 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -13,6 +13,11 @@ const inter = Inter({ export const metadata: Metadata = { title: "Open SWE", description: "Open SWE UX by LangChain", + icons: { + icon: "/favicon.png", + shortcut: "/favicon.png", + apple: "/favicon.png", + }, }; export default function RootLayout({ diff --git a/apps/web/src/components/configuration-sidebar/config-field.tsx b/apps/web/src/components/configuration-sidebar/config-field.tsx new file mode 100644 index 00000000..2c78968d --- /dev/null +++ b/apps/web/src/components/configuration-sidebar/config-field.tsx @@ -0,0 +1,291 @@ +"use client"; + +import { useState } from "react"; +import { Slider } from "@/components/ui/slider"; +import { Switch } from "@/components/ui/switch"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Label } from "@/components/ui/label"; +import { useConfigStore } from "@/hooks/use-config-store"; +import { AlertCircle } from "lucide-react"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { Button } from "@/components/ui/button"; +import _ from "lodash"; +import { cn } from "@/lib/utils"; + +interface Option { + label: string; + value: string; +} + +interface ConfigFieldProps { + id: string; + label: string; + type: + | "text" + | "textarea" + | "number" + | "switch" + | "slider" + | "select" + | "json"; + description?: string; + placeholder?: string; + options?: Option[]; + min?: number; + max?: number; + step?: number; + className?: string; + // Optional props for external state management + value?: any; + setValue?: (value: any) => void; +} + +export function ConfigField({ + id, + label, + type, + description, + placeholder, + options = [], + min, + max, + step = 1, + className, + value: externalValue, // TODO: Rename to avoid conflict + setValue: externalSetValue, // TODO: Rename to avoid conflict +}: ConfigFieldProps) { + const store = useConfigStore(); + const [jsonError, setJsonError] = useState(null); + + // Determine whether to use external state or Zustand store + const isExternallyManaged = externalSetValue !== undefined; + + const currentValue = isExternallyManaged + ? externalValue + : store.configs[id]?.[id]; + + const handleChange = (newValue: any) => { + setJsonError(null); + if (isExternallyManaged && externalSetValue) { + externalSetValue(newValue); + } else { + store.updateConfig(id, newValue); + } + }; + + const handleJsonChange = (jsonString: string) => { + try { + if (!jsonString.trim()) { + handleChange(undefined); + setJsonError(null); + return; + } + + const parsedJson = JSON.parse(jsonString); + + handleChange(parsedJson); + setJsonError(null); + } catch { + if (isExternallyManaged && externalSetValue) { + externalSetValue(jsonString); + } else { + store.updateConfig(id, jsonString); + } + setJsonError("Invalid JSON format"); + } + }; + + const handleFormatJson = (jsonString: string) => { + try { + const parsed = JSON.parse(jsonString); + handleChange(parsed); + setJsonError(null); + } catch { + setJsonError("Invalid JSON format"); + } + }; + + return ( +
+
+ + {type === "switch" && ( + + )} +
+ + {description && ( +

+ {description} +

+ )} + + {type === "text" && ( + handleChange(e.target.value)} + placeholder={placeholder} + /> + )} + + {type === "textarea" && ( +