Skip to content

Commit 25850df

Browse files
authored
feat: initial setup wizard (#1693)
1 parent e22497e commit 25850df

File tree

7 files changed

+516
-87
lines changed

7 files changed

+516
-87
lines changed

ui/src/App.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { ErrorModalProvider } from './components/ui/error-modal';
99
import { ToastProvider } from './components/ui/simple-toast';
1010
import { AppBarContext } from './contexts/AppBarContext';
1111
import { AuthProvider } from './contexts/AuthContext';
12-
import { Config, ConfigContext } from './contexts/ConfigContext';
12+
import { Config, ConfigContext, ConfigUpdateContext } from './contexts/ConfigContext';
1313
import { PageContextProvider } from './contexts/PageContext';
1414
import { SchemaProvider } from './contexts/SchemaContext';
1515
import { SearchStateProvider } from './contexts/SearchStateContext';
@@ -92,7 +92,12 @@ function DeveloperElement({
9292
return <ProtectedRoute requiredRole="developer">{children}</ProtectedRoute>;
9393
}
9494

95-
function AppInner({ config }: Props): React.ReactElement {
95+
function AppInner({ config: initialConfig }: Props): React.ReactElement {
96+
const [config, setConfig] = React.useState(initialConfig);
97+
const updateConfig = React.useCallback((patch: Partial<Config>) => {
98+
setConfig((prev) => ({ ...prev, ...patch }));
99+
}, []);
100+
96101
const [title, setTitle] = React.useState<string>('');
97102
const { preferences } = useUserPreferences();
98103
const theme = preferences.theme || 'dark';
@@ -144,7 +149,8 @@ function AppInner({ config }: Props): React.ReactElement {
144149
}}
145150
>
146151
<ConfigContext.Provider value={config}>
147-
<AuthProvider>
152+
<ConfigUpdateContext.Provider value={updateConfig}>
153+
<AuthProvider>
148154
<SearchStateProvider>
149155
<SchemaProvider>
150156
<ErrorModalProvider>
@@ -197,7 +203,8 @@ function AppInner({ config }: Props): React.ReactElement {
197203
</ErrorModalProvider>
198204
</SchemaProvider>
199205
</SearchStateProvider>
200-
</AuthProvider>
206+
</AuthProvider>
207+
</ConfigUpdateContext.Provider>
201208
</ConfigContext.Provider>
202209
</AppBarContext.Provider>
203210
</SWRConfig>

ui/src/contexts/ConfigContext.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ export type Config = {
4545

4646
export const ConfigContext = createContext<Config>(null!);
4747

48+
export const ConfigUpdateContext = createContext<(patch: Partial<Config>) => void>(() => {});
49+
4850
/**
4951
* Access the application configuration from the nearest ConfigContext provider.
5052
*
@@ -53,3 +55,7 @@ export const ConfigContext = createContext<Config>(null!);
5355
export function useConfig(): Config {
5456
return useContext(ConfigContext);
5557
}
58+
59+
export function useUpdateConfig(): (patch: Partial<Config>) => void {
60+
return useContext(ConfigUpdateContext);
61+
}

ui/src/features/agent/components/AgentChatModal.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ function findLatestSession(
3737
}
3838

3939
export function AgentChatModal(): ReactElement | null {
40-
const { isOpen, isClosing, closeChat } = useAgentChatContext();
40+
const { isOpen, isClosing, closeChat, initialInputValue, setInitialInputValue } = useAgentChatContext();
4141
const isMobile = useIsMobile();
4242
const {
4343
sessionId,
@@ -116,10 +116,11 @@ export function AgentChatModal(): ReactElement | null {
116116

117117
const handleSend = useCallback(
118118
(message: string, dagContexts?: DAGContext[], model?: string): void => {
119+
setInitialInputValue(null);
119120
// sendMessage handles its own error reporting via setError internally
120121
sendMessage(message, model, dagContexts).catch(() => {});
121122
},
122-
[sendMessage]
123+
[sendMessage, setInitialInputValue]
123124
);
124125

125126
const handleCancel = useCallback((): void => {
@@ -208,6 +209,7 @@ export function AgentChatModal(): ReactElement | null {
208209
onCancel={handleCancel}
209210
isWorking={isWorking}
210211
placeholder="Ask me to create a DAG, run a command..."
212+
initialValue={initialInputValue}
211213
/>
212214
</div>
213215
</div>

ui/src/features/agent/components/ChatInput.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ interface ChatInputProps {
2727
isWorking: boolean;
2828
disabled?: boolean;
2929
placeholder?: string;
30+
initialValue?: string | null;
3031
}
3132

3233
export function ChatInput({
@@ -35,6 +36,7 @@ export function ChatInput({
3536
isWorking,
3637
disabled,
3738
placeholder = 'Type a message...',
39+
initialValue,
3840
}: ChatInputProps) {
3941
const client = useClient();
4042
const appBarContext = useContext(AppBarContext);
@@ -88,6 +90,20 @@ export function ChatInput({
8890
return () => controller.abort();
8991
}, [client, appBarContext.selectedRemoteNode]);
9092

93+
// Pre-fill textarea with initial value (e.g., from setup wizard)
94+
useEffect(() => {
95+
if (initialValue) {
96+
setMessage(initialValue);
97+
requestAnimationFrame(() => {
98+
const el = textareaRef.current;
99+
if (el) {
100+
el.style.height = 'auto';
101+
el.style.height = `${Math.min(el.scrollHeight, 120)}px`;
102+
}
103+
});
104+
}
105+
}, [initialValue]);
106+
91107
// Reset pending state when server confirms processing or after timeout fallback
92108
useEffect(() => {
93109
if (isWorking) {

ui/src/features/agent/context/AgentChatContext.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ interface AgentChatContextType {
3535
addMessage: (message: Message) => void;
3636
setPendingUserMessage: (message: string | null) => void;
3737
clearSession: () => void;
38+
initialInputValue: string | null;
39+
setInitialInputValue: (value: string | null) => void;
3840
}
3941

4042
interface AgentChatProviderProps {
@@ -58,6 +60,7 @@ export function AgentChatProvider({ children }: AgentChatProviderProps): ReactNo
5860
const [sessions, setSessions] = useState<SessionWithState[]>([]);
5961
const [hasMoreSessions, setHasMoreSessions] = useState(true);
6062
const [sessionPage, setSessionPage] = useState(1);
63+
const [initialInputValue, setInitialInputValue] = useState<string | null>(null);
6164

6265
const appendSessions = useCallback((newSessions: SessionWithState[]) => {
6366
setSessions(prev => [...prev, ...newSessions]);
@@ -140,13 +143,16 @@ export function AgentChatProvider({ children }: AgentChatProviderProps): ReactNo
140143
addMessage,
141144
setPendingUserMessage,
142145
clearSession,
146+
initialInputValue,
147+
setInitialInputValue,
143148
}), [
144149
isOpen, isClosing, sessionId, messages, pendingUserMessage,
145150
sessionState, sessions, hasMoreSessions, sessionPage,
146151
openChat, closeChat, toggleChat,
147152
setSessionId, setMessages, setSessionState, setSessions,
148153
appendSessions, setHasMoreSessions, setSessionPage,
149154
addMessage, setPendingUserMessage, clearSession,
155+
initialInputValue,
150156
]);
151157

152158
return (

ui/src/layouts/Layout.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { getResponsiveTitleClass } from '@/lib/text-utils';
55
import { Menu, Terminal, X } from 'lucide-react';
66
import { useAgentChatContext } from '@/features/agent';
77
import * as React from 'react';
8+
import { useLocation, useNavigate } from 'react-router-dom';
89
import { mainListItems as MainListItems } from '../menu';
910

1011
/**
@@ -77,7 +78,19 @@ type LayoutProps = {
7778
*/
7879
function Content({ navbarColor, children }: LayoutProps) {
7980
const config = useConfig();
80-
const { toggleChat } = useAgentChatContext();
81+
const { toggleChat, openChat, setInitialInputValue } = useAgentChatContext();
82+
const location = useLocation();
83+
const navigate = useNavigate();
84+
85+
// Auto-open agent modal when redirected from setup with openAgent state
86+
React.useEffect(() => {
87+
if ((location.state as { openAgent?: boolean })?.openAgent) {
88+
setInitialInputValue('Create a simple DAG that prints Hello World every minute');
89+
openChat();
90+
navigate(location.pathname, { replace: true, state: {} });
91+
}
92+
}, [location.state, location.pathname, openChat, setInitialInputValue, navigate]);
93+
8194
const hasCustomColor: boolean = Boolean(
8295
navbarColor && navbarColor.trim() !== ''
8396
);

0 commit comments

Comments
 (0)