feat: Hookup config store (#99)

* feat: Hookup config store

* cr
This commit is contained in:
Brace Sproul 2025-06-09 16:41:52 -07:00 • committed by GitHub
parent b3bcc46c59
commit 1c12f6f754
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 63 additions and 70 deletions

View file

@ -13,12 +13,13 @@ import {
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { useConfigStore } from "@/hooks/use-config-store"; import { DEFAULT_CONFIG_KEY, useConfigStore } from "@/hooks/use-config-store";
import { AlertCircle } from "lucide-react"; import { AlertCircle } from "lucide-react";
import { Alert, AlertDescription } from "@/components/ui/alert"; import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import _ from "lodash"; import _ from "lodash";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { useQueryState } from "nuqs";
interface Option { interface Option {
label: string; label: string;
@ -62,6 +63,8 @@ export function ConfigField({
value: externalValue, // TODO: Rename to avoid conflict value: externalValue, // TODO: Rename to avoid conflict
setValue: externalSetValue, // TODO: Rename to avoid conflict setValue: externalSetValue, // TODO: Rename to avoid conflict
}: ConfigFieldProps) { }: ConfigFieldProps) {
const [threadId] = useQueryState("threadId");
const configKey = threadId || DEFAULT_CONFIG_KEY;
const store = useConfigStore(); const store = useConfigStore();
const [jsonError, setJsonError] = useState<string | null>(null); const [jsonError, setJsonError] = useState<string | null>(null);
@ -70,14 +73,14 @@ export function ConfigField({
const currentValue = isExternallyManaged const currentValue = isExternallyManaged
? externalValue ? externalValue
: store.configs[id]?.[id]; : store.configs[configKey]?.[id];
const handleChange = (newValue: any) => { const handleChange = (newValue: any) => {
setJsonError(null); setJsonError(null);
if (isExternallyManaged && externalSetValue) { if (isExternallyManaged && externalSetValue) {
externalSetValue(newValue); externalSetValue(newValue);
} else { } else {
store.updateConfig(id, newValue); store.updateConfig(configKey, id, newValue);
} }
}; };
@ -97,7 +100,7 @@ export function ConfigField({
if (isExternallyManaged && externalSetValue) { if (isExternallyManaged && externalSetValue) {
externalSetValue(jsonString); externalSetValue(jsonString);
} else { } else {
store.updateConfig(id, jsonString); store.updateConfig(configKey, id, jsonString);
} }
setJsonError("Invalid JSON format"); setJsonError("Invalid JSON format");
} }

View file

@ -4,7 +4,7 @@ import { forwardRef, ForwardedRef, useState, useEffect } from "react";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { ConfigField } from "@/components/configuration-sidebar/config-field"; import { ConfigField } from "@/components/configuration-sidebar/config-field";
import { ConfigSection } from "@/components/configuration-sidebar/config-section"; import { ConfigSection } from "@/components/configuration-sidebar/config-section";
import { useConfigStore } from "@/hooks/use-config-store"; import { useConfigStore, DEFAULT_CONFIG_KEY } from "@/hooks/use-config-store";
import { ScrollArea } from "@/components/ui/scroll-area"; import { ScrollArea } from "@/components/ui/scroll-area";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { Skeleton } from "@/components/ui/skeleton"; import { Skeleton } from "@/components/ui/skeleton";
@ -12,11 +12,15 @@ import type { ConfigurableFieldUIMetadata } from "@open-swe/shared/configurable-
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { PanelRightOpen } from "lucide-react"; import { PanelRightOpen } from "lucide-react";
import { GraphConfigurationMetadata } from "@open-swe/shared/open-swe/types"; import { GraphConfigurationMetadata } from "@open-swe/shared/open-swe/types";
import { useQueryState } from "nuqs";
import { useStreamContext } from "@/providers/Stream";
/** /**
* Extract configuration metadata from the GraphConfiguration Zod schema * Extract configuration metadata from the GraphConfiguration Zod schema
*/ */
function extractConfigurationsFromSchema(): ConfigurableFieldUIMetadata[] { function extractConfigurationsFromSchema(
configurable: Record<string, any>,
): ConfigurableFieldUIMetadata[] {
const configurations: ConfigurableFieldUIMetadata[] = []; const configurations: ConfigurableFieldUIMetadata[] = [];
for (const [label, { x_open_swe_ui_config: metadata }] of Object.entries( for (const [label, { x_open_swe_ui_config: metadata }] of Object.entries(
@ -28,7 +32,7 @@ function extractConfigurationsFromSchema(): ConfigurableFieldUIMetadata[] {
configurations.push({ configurations.push({
label, label,
type: metadata.type, type: metadata.type,
default: metadata.default, default: configurable[label] || metadata.default,
description: metadata.description, description: metadata.description,
placeholder: metadata.placeholder, placeholder: metadata.placeholder,
options: metadata.options, options: metadata.options,
@ -52,6 +56,9 @@ export const ConfigurationSidebar = forwardRef<
AIConfigPanelProps AIConfigPanelProps
>(({ className, open, onClose }, ref: ForwardedRef<HTMLDivElement>) => { >(({ className, open, onClose }, ref: ForwardedRef<HTMLDivElement>) => {
const { configs, updateConfig } = useConfigStore(); const { configs, updateConfig } = useConfigStore();
const stream = useStreamContext();
const [threadId] = useQueryState("threadId");
const [configurations, setConfigurations] = useState< const [configurations, setConfigurations] = useState<
ConfigurableFieldUIMetadata[] ConfigurableFieldUIMetadata[]
@ -60,17 +67,38 @@ export const ConfigurationSidebar = forwardRef<
useEffect(() => { useEffect(() => {
setLoading(true); setLoading(true);
const configKey = threadId || DEFAULT_CONFIG_KEY;
const actualConfigs = extractConfigurationsFromSchema(); if (threadId) {
actualConfigs.forEach((config) => { stream.client.threads.get(threadId).then((t) => {
if (configs[config.label] === undefined && config.default !== undefined) { if (
updateConfig(config.label, config.default); !("config" in t) ||
} !(t as any).config ||
}); !(t as any).config.configurable
) {
console.error("Thread does not have config key", t);
return;
}
setConfigurations(actualConfigs); const actualConfigs = extractConfigurationsFromSchema(
(t.config as any).configurable,
);
actualConfigs.forEach((c) => {
// Always update the config store with either the default values, or the values from the thread.
updateConfig(configKey, c.label, c.default);
});
setConfigurations(actualConfigs);
});
} else {
const actualConfigs = extractConfigurationsFromSchema({});
actualConfigs.forEach((c) => {
updateConfig(configKey, c.label, c.default);
});
setConfigurations(actualConfigs);
}
setLoading(false); setLoading(false);
}, [configs, updateConfig]); }, [threadId]);
return ( return (
<div <div

View file

@ -42,7 +42,7 @@ import { GitHubOAuthButton } from "../github/github-oauth-button";
import { useGitHubApp } from "@/hooks/useGitHubApp"; import { useGitHubApp } from "@/hooks/useGitHubApp";
import TaskList from "../task-list"; import TaskList from "../task-list";
import { ConfigurationSidebar } from "../configuration-sidebar"; import { ConfigurationSidebar } from "../configuration-sidebar";
import { useConfigStore } from "@/hooks/use-config-store"; import { DEFAULT_CONFIG_KEY, useConfigStore } from "@/hooks/use-config-store";
import { RepositoryBranchSelectors } from "../github/repo-branch-selectors"; import { RepositoryBranchSelectors } from "../github/repo-branch-selectors";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { import {
@ -101,7 +101,7 @@ export function Thread() {
const [artifactContext, setArtifactContext] = useArtifactContext(); const [artifactContext, setArtifactContext] = useArtifactContext();
const [artifactOpen, closeArtifact] = useArtifactOpen(); const [artifactOpen, closeArtifact] = useArtifactOpen();
const { selectedRepository } = useGitHubApp(); const { selectedRepository } = useGitHubApp();
const { getConfigs } = useConfigStore(); const { getConfig } = useConfigStore();
const { const {
taskPlan, taskPlan,
handleTaskChange, handleTaskChange,
@ -269,7 +269,7 @@ export function Thread() {
config: { config: {
recursion_limit: 400, recursion_limit: 400,
configurable: { configurable: {
...getConfigs(), ...getConfig(threadId || DEFAULT_CONFIG_KEY),
}, },
}, },
metadata: { metadata: {
@ -294,7 +294,7 @@ export function Thread() {
config: { config: {
recursion_limit: 400, recursion_limit: 400,
configurable: { configurable: {
...getConfigs(), ...getConfig(threadId || DEFAULT_CONFIG_KEY),
}, },
}, },
metadata: { metadata: {

View file

@ -114,8 +114,6 @@ export function AssistantMessage({
return null; return null;
} }
console.log("returning");
return ( return (
<div className="group mr-auto flex w-full max-w-3xl items-start gap-2"> <div className="group mr-auto flex w-full max-w-3xl items-start gap-2">
<div className="flex w-full flex-col gap-2"> <div className="flex w-full flex-col gap-2">

View file

@ -2,21 +2,16 @@
import { create } from "zustand"; import { create } from "zustand";
import { persist } from "zustand/middleware"; import { persist } from "zustand/middleware";
import type { ConfigurableFieldUIMetadata } from "@open-swe/shared/configurable-metadata";
export const DEFAULT_CONFIG_KEY = "open_swe_default_config"; export const DEFAULT_CONFIG_KEY = "open_swe_default_config";
interface ConfigState { interface ConfigState {
configs: Record<string, any>; configs: Record<string, any>;
getConfig: (key: string) => Record<string, any>; getConfig: (namespace: string) => Record<string, any>;
getConfigs: () => Record<string, any>; getConfigs: () => Record<string, any>;
updateConfig: (key: string, value: any) => void; updateConfig: (namespace: string, key: string, value: any) => void;
resetConfig: (key: string) => void; resetConfig: (namespace: string) => void;
setDefaultConfig: ( resetStore: (namespace: string) => void;
key: string,
configurations: ConfigurableFieldUIMetadata[],
) => void;
resetStore: (key: string) => void;
} }
export const useConfigStore = create<ConfigState>()( export const useConfigStore = create<ConfigState>()(
@ -24,9 +19,9 @@ export const useConfigStore = create<ConfigState>()(
(set, get) => ({ (set, get) => ({
configs: {}, configs: {},
getConfig: (key: string) => { getConfig: (namespace: string) => {
const state = get(); const state = get();
const baseConfig = state.configs[key]; const baseConfig = state.configs[namespace];
const configObj = { const configObj = {
...baseConfig, ...baseConfig,
}; };
@ -36,33 +31,23 @@ export const useConfigStore = create<ConfigState>()(
getConfigs: () => { getConfigs: () => {
const state = get(); const state = get();
const flatConfigs: Record<string, any> = {}; return state.configs;
Object.entries(state.configs).forEach(([key, configObj]) => {
if (
configObj &&
typeof configObj === "object" &&
configObj[key] !== undefined
) {
flatConfigs[key] = configObj[key];
}
});
return flatConfigs;
}, },
updateConfig: (key, value) => updateConfig: (namespace: string, key: string, value: any) =>
set((state) => ({ set((state) => ({
configs: { configs: {
...state.configs, ...state.configs,
[key]: { [namespace]: {
...(state.configs[key] || {}), ...(state.configs[namespace] || {}),
[key]: value, [key]: value,
}, },
}, },
})), })),
resetConfig: (key: string) => { resetConfig: (namespace: string) => {
set((state) => { set((state) => {
const config = state.configs[key]; const config = state.configs[namespace];
if (!config || !config.__defaultValues) { if (!config || !config.__defaultValues) {
return state; return state;
} }
@ -70,33 +55,12 @@ export const useConfigStore = create<ConfigState>()(
return { return {
configs: { configs: {
...state.configs, ...state.configs,
[key]: defaultsToUse, [namespace]: defaultsToUse,
}, },
}; };
}); });
}, },
setDefaultConfig: (
key: string,
configurations: ConfigurableFieldUIMetadata[],
) => {
const defaultConfig: Record<string, any> = {};
configurations.forEach((config: ConfigurableFieldUIMetadata) => {
if (config.default !== undefined) {
defaultConfig[config.label] = config.default;
}
});
defaultConfig.__defaultValues = { ...defaultConfig };
set((currentState) => ({
configs: {
...currentState.configs,
[key]: defaultConfig,
},
}));
},
resetStore: () => set({ configs: {} }), resetStore: () => set({ configs: {} }),
}), }),
{ {