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,
} from "@/components/ui/select";
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 { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import _ from "lodash";
import { cn } from "@/lib/utils";
import { useQueryState } from "nuqs";
interface Option {
label: string;
@ -62,6 +63,8 @@ export function ConfigField({
value: externalValue, // TODO: Rename to avoid conflict
setValue: externalSetValue, // TODO: Rename to avoid conflict
}: ConfigFieldProps) {
const [threadId] = useQueryState("threadId");
const configKey = threadId || DEFAULT_CONFIG_KEY;
const store = useConfigStore();
const [jsonError, setJsonError] = useState<string | null>(null);
@ -70,14 +73,14 @@ export function ConfigField({
const currentValue = isExternallyManaged
? externalValue
: store.configs[id]?.[id];
: store.configs[configKey]?.[id];
const handleChange = (newValue: any) => {
setJsonError(null);
if (isExternallyManaged && externalSetValue) {
externalSetValue(newValue);
} else {
store.updateConfig(id, newValue);
store.updateConfig(configKey, id, newValue);
}
};
@ -97,7 +100,7 @@ export function ConfigField({
if (isExternallyManaged && externalSetValue) {
externalSetValue(jsonString);
} else {
store.updateConfig(id, jsonString);
store.updateConfig(configKey, id, jsonString);
}
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 { ConfigField } from "@/components/configuration-sidebar/config-field";
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 { cn } from "@/lib/utils";
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 { PanelRightOpen } from "lucide-react";
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
*/
function extractConfigurationsFromSchema(): ConfigurableFieldUIMetadata[] {
function extractConfigurationsFromSchema(
configurable: Record<string, any>,
): ConfigurableFieldUIMetadata[] {
const configurations: ConfigurableFieldUIMetadata[] = [];
for (const [label, { x_open_swe_ui_config: metadata }] of Object.entries(
@ -28,7 +32,7 @@ function extractConfigurationsFromSchema(): ConfigurableFieldUIMetadata[] {
configurations.push({
label,
type: metadata.type,
default: metadata.default,
default: configurable[label] || metadata.default,
description: metadata.description,
placeholder: metadata.placeholder,
options: metadata.options,
@ -52,6 +56,9 @@ export const ConfigurationSidebar = forwardRef<
AIConfigPanelProps
>(({ className, open, onClose }, ref: ForwardedRef<HTMLDivElement>) => {
const { configs, updateConfig } = useConfigStore();
const stream = useStreamContext();
const [threadId] = useQueryState("threadId");
const [configurations, setConfigurations] = useState<
ConfigurableFieldUIMetadata[]
@ -60,17 +67,38 @@ export const ConfigurationSidebar = forwardRef<
useEffect(() => {
setLoading(true);
const configKey = threadId || DEFAULT_CONFIG_KEY;
const actualConfigs = extractConfigurationsFromSchema();
actualConfigs.forEach((config) => {
if (configs[config.label] === undefined && config.default !== undefined) {
updateConfig(config.label, config.default);
}
});
if (threadId) {
stream.client.threads.get(threadId).then((t) => {
if (
!("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);
}, [configs, updateConfig]);
}, [threadId]);
return (
<div

View file

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

View file

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

View file

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