diff --git a/apps/agent/entrypoints/sidepanel/index/ChatProviderSelector.tsx b/apps/agent/components/chat/ChatProviderSelector.tsx similarity index 98% rename from apps/agent/entrypoints/sidepanel/index/ChatProviderSelector.tsx rename to apps/agent/components/chat/ChatProviderSelector.tsx index 3ddcb1517..b9897cf60 100644 --- a/apps/agent/entrypoints/sidepanel/index/ChatProviderSelector.tsx +++ b/apps/agent/components/chat/ChatProviderSelector.tsx @@ -17,7 +17,7 @@ import { import { BrowserOSIcon, ProviderIcon } from '@/lib/llm-providers/providerIcons' import type { ProviderType } from '@/lib/llm-providers/types' import { cn } from '@/lib/utils' -import type { Provider } from './chatTypes' +import type { Provider } from './chatComponentTypes' interface ChatProviderSelectorProps { providers: Provider[] diff --git a/apps/agent/components/chat/chatComponentTypes.ts b/apps/agent/components/chat/chatComponentTypes.ts new file mode 100644 index 000000000..dc0e8f3ec --- /dev/null +++ b/apps/agent/components/chat/chatComponentTypes.ts @@ -0,0 +1,7 @@ +import type { ProviderType } from '@/lib/llm-providers/types' + +export interface Provider { + id: string + name: string + type: ProviderType +} diff --git a/apps/agent/entrypoints/options/create-graph/CreateGraph.tsx b/apps/agent/entrypoints/options/create-graph/CreateGraph.tsx index 9a1c128ce..52ad337d0 100644 --- a/apps/agent/entrypoints/options/create-graph/CreateGraph.tsx +++ b/apps/agent/entrypoints/options/create-graph/CreateGraph.tsx @@ -5,6 +5,17 @@ import type { FC, FormEvent } from 'react' import { useEffect, useRef, useState } from 'react' import { useSearchParams } from 'react-router' import useDeepCompareEffect from 'use-deep-compare-effect' +import type { Provider } from '@/components/chat/chatComponentTypes' +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@/components/ui/alert-dialog' import { ResizableHandle, ResizablePanel, @@ -12,11 +23,13 @@ import { } from '@/components/ui/resizable' import { useChatRefs } from '@/entrypoints/sidepanel/index/useChatRefs' import { useAgentServerUrl } from '@/lib/browseros/useBrowserOSProviders' +import { useLlmProviders } from '@/lib/llm-providers/useLlmProviders' import { useRpcClient } from '@/lib/rpc/RpcClientProvider' import { sentry } from '@/lib/sentry/sentry' import { useWorkflows } from '@/lib/workflows/workflowStorage' import { GraphCanvas } from './GraphCanvas' import { GraphChat } from './GraphChat' +import { WorkflowsChatHeader } from './WorkflowsChatHeader' type MessageType = 'create-graph' | 'update-graph' | 'run-graph' @@ -68,8 +81,10 @@ export const CreateGraph: FC = () => { >(undefined) const [query, setQuery] = useState('') + const [showDiscardDialog, setShowDiscardDialog] = useState(false) const { workflows, addWorkflow, editWorkflow } = useWorkflows() + const { providers: llmProviders, setDefaultProvider } = useLlmProviders() const rpcClient = useRpcClient() // Initialize edit mode when workflowId is provided @@ -147,6 +162,8 @@ export const CreateGraph: FC = () => { enabledMcpServersRef, enabledCustomServersRef, personalizationRef, + selectedLlmProvider, + isLoadingProviders, } = useChatRefs() const agentUrlRef = useRef(agentServerUrl) @@ -158,7 +175,7 @@ export const CreateGraph: FC = () => { codeIdRef.current = codeId }, [agentServerUrl, codeId]) - const { sendMessage, stop, status, messages, error } = useChat({ + const { sendMessage, stop, status, messages, error, setMessages } = useChat({ transport: new DefaultChatTransport({ prepareSendMessagesRequest: async ({ messages }) => { const lastMessage = messages[messages.length - 1] @@ -285,6 +302,60 @@ export const CreateGraph: FC = () => { } } + // Provider data for header + const providers: Provider[] = llmProviders.map((p) => ({ + id: p.id, + name: p.name, + type: p.type, + })) + + const selectedProviderForHeader: Provider | undefined = selectedLlmProvider + ? { + id: selectedLlmProvider.id, + name: selectedLlmProvider.name, + type: selectedLlmProvider.type, + } + : providers[0] + + // Has generated code but can't auto-save (no name) + const hasUnsavedWork = codeId && !graphName + + const resetToNewWorkflow = () => { + setCodeId(undefined) + setGraphData(undefined) + setGraphName('') + setSavedWorkflowId(undefined) + setSavedCodeId(undefined) + setMessages([]) + } + + const handleSelectProvider = (provider: Provider) => { + setDefaultProvider(provider.id) + } + + const handleNewWorkflow = async () => { + // Can auto-save: has name AND code + if (graphName && codeId) { + await onClickSave() + resetToNewWorkflow() + return + } + + // Has unsaved work that can't be auto-saved: show confirmation + if (hasUnsavedWork) { + setShowDiscardDialog(true) + return + } + + // Nothing to save, just reset + resetToNewWorkflow() + } + + const handleConfirmDiscard = () => { + setShowDiscardDialog(false) + resetToNewWorkflow() + } + useDeepCompareEffect(() => { if (status === 'ready' && lastAssistantMessageWithGraph) { const metadata = lastAssistantMessageWithGraph.metadata as @@ -295,10 +366,10 @@ export const CreateGraph: FC = () => { } }, [status, lastAssistantMessageWithGraph ?? {}]) - if (!isInitialized) { + if (!isInitialized || isLoadingProviders || !selectedProviderForHeader) { return (
-
Loading workflow...
+
Loading...
) } @@ -335,18 +406,47 @@ export const CreateGraph: FC = () => { maxSize={'70%'} minSize={'30%'} > - +
+ 0} + /> +
+ +
+
+ + + + + Discard unsaved workflow? + + You have an unsaved workflow. Creating a new one will discard your + current changes. + + + + Cancel + + Discard + + + + ) } diff --git a/apps/agent/entrypoints/options/create-graph/WorkflowsChatHeader.tsx b/apps/agent/entrypoints/options/create-graph/WorkflowsChatHeader.tsx new file mode 100644 index 000000000..643ed4b05 --- /dev/null +++ b/apps/agent/entrypoints/options/create-graph/WorkflowsChatHeader.tsx @@ -0,0 +1,92 @@ +import { Github, Plus, SettingsIcon } from 'lucide-react' +import type { FC } from 'react' +import { ChatProviderSelector } from '@/components/chat/ChatProviderSelector' +import type { Provider } from '@/components/chat/chatComponentTypes' +import { ThemeToggle } from '@/components/elements/theme-toggle' +import { productRepositoryUrl } from '@/lib/constants/productUrls' +import { BrowserOSIcon, ProviderIcon } from '@/lib/llm-providers/providerIcons' +import type { ProviderType } from '@/lib/llm-providers/types' + +interface WorkflowsChatHeaderProps { + selectedProvider: Provider + providers: Provider[] + onSelectProvider: (provider: Provider) => void + onNewWorkflow: () => void + hasMessages: boolean +} + +export const WorkflowsChatHeader: FC = ({ + selectedProvider, + providers, + onSelectProvider, + onNewWorkflow, + hasMessages, +}) => { + return ( +
+
+ + + +
+ +
+ {hasMessages && ( + + )} + + + + + + + + + + +
+
+ ) +} diff --git a/apps/agent/entrypoints/sidepanel/index/ChatHeader.tsx b/apps/agent/entrypoints/sidepanel/index/ChatHeader.tsx index fcc01e09e..95d80e976 100644 --- a/apps/agent/entrypoints/sidepanel/index/ChatHeader.tsx +++ b/apps/agent/entrypoints/sidepanel/index/ChatHeader.tsx @@ -1,11 +1,11 @@ import { Github, Plus, SettingsIcon } from 'lucide-react' import type { FC } from 'react' +import { ChatProviderSelector } from '@/components/chat/ChatProviderSelector' +import type { Provider } from '@/components/chat/chatComponentTypes' import { ThemeToggle } from '@/components/elements/theme-toggle' import { productRepositoryUrl } from '@/lib/constants/productUrls' import { BrowserOSIcon, ProviderIcon } from '@/lib/llm-providers/providerIcons' import type { ProviderType } from '@/lib/llm-providers/types' -import { ChatProviderSelector } from './ChatProviderSelector' -import type { Provider } from './chatTypes' interface ChatHeaderProps { selectedProvider: Provider diff --git a/apps/agent/entrypoints/sidepanel/index/chatTypes.ts b/apps/agent/entrypoints/sidepanel/index/chatTypes.ts index 59abb444c..8e4be0340 100644 --- a/apps/agent/entrypoints/sidepanel/index/chatTypes.ts +++ b/apps/agent/entrypoints/sidepanel/index/chatTypes.ts @@ -1,13 +1,5 @@ -import type { ProviderType } from '@/lib/llm-providers/types' - export type ChatMode = 'chat' | 'agent' -export interface Provider { - id: string - name: string - type: ProviderType -} - export interface Suggestion { display: string prompt: string diff --git a/apps/agent/entrypoints/sidepanel/index/useChatSession.ts b/apps/agent/entrypoints/sidepanel/index/useChatSession.ts index 497c60198..bdd1b0b13 100644 --- a/apps/agent/entrypoints/sidepanel/index/useChatSession.ts +++ b/apps/agent/entrypoints/sidepanel/index/useChatSession.ts @@ -3,6 +3,7 @@ import { DefaultChatTransport, type UIMessage } from 'ai' import { compact } from 'es-toolkit/array' import { useEffect, useRef, useState } from 'react' import useDeepCompareEffect from 'use-deep-compare-effect' +import type { Provider } from '@/components/chat/chatComponentTypes' import { useAgentServerUrl } from '@/lib/browseros/useBrowserOSProviders' import type { ChatAction } from '@/lib/chat-actions/types' import { @@ -15,7 +16,7 @@ import { import { useLlmProviders } from '@/lib/llm-providers/useLlmProviders' import { track } from '@/lib/metrics/track' import { searchActionsStorage } from '@/lib/search-actions/searchActionsStorage' -import type { ChatMode, Provider } from './chatTypes' +import type { ChatMode } from './chatTypes' import { useChatRefs } from './useChatRefs' import { useNotifyActiveTab } from './useNotifyActiveTab'