Add the assistant: a chat that drives the MCP tools in-process
Docs / docs (push) Successful in 42s
Playwright Tests / test-playwright (1, 2) (push) Failing after 2m42s
Playwright Tests / test-playwright (2, 2) (push) Failing after 10s
pre-commit / pre-commit (push) Failing after 2m33s
Test Backend / test-backend (push) Failing after 2m48s
Compose Smoke Test / test-compose (push) Failing after 9s
Playwright Tests / merge-reports (push) Failing after 2m17s

A provider set under Settings (OpenAI-compatible; key encrypted in
flow-data/ai.json) turns on an Assistant panel in the sidebar. Each turn runs
the FastMCP tools in-process as the person chatting, streams the rounds over
SSE and keeps one transcript per user. The panel floats on any screen and
shares the editor's lane: a settings panel keeps the top half, the chat the
bottom. The MCP client is now built once, for the endpoint and the chat.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-09-16 23:28:23 +02:00
co-authored by Claude Fable 5.1
parent d9630f66f5
commit cc4091166e
25 changed files with 1957 additions and 43 deletions
+28 -3
View File
@@ -2,7 +2,7 @@ import { Info, X } from "lucide-react"
import { AnimatePresence, motion } from "motion/react"
import type { ReactNode } from "react"
import { useEffect, useState } from "react"
import { chatStore, useChat } from "@/components/Chat/chatStore"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
@@ -180,6 +180,8 @@ export function PanelTitle({
* for that.
*
* Node settings and flow settings share it, so the two read as one surface.
* The assistant shares it too, and the lane: while it is open, a settings
* panel keeps the top half and the chat takes the bottom.
*/
export function SidePanel({
open,
@@ -187,6 +189,8 @@ export function SidePanel({
testId,
bodyKey,
expanded = false,
fixed = false,
stacked,
header,
footer,
children,
@@ -200,12 +204,26 @@ export function SidePanel({
bodyKey: string
/** Take the width a code editor needs, still floating over the canvas. */
expanded?: boolean
/** Pinned to the viewport rather than the canvas: a panel on any screen. */
fixed?: boolean
/**
* Which half of the lane to keep. Left unset, the panel counts itself as
* open and keeps the top whenever the assistant is open beside it.
*/
stacked?: "top" | "bottom" | false
header: ReactNode
footer?: ReactNode
children: ReactNode
onClose: () => void
}) {
const isMobile = useIsMobile()
const chat = useChat()
const shares = stacked === undefined
useEffect(() => {
if (!shares || !open || isMobile) return
return chatStore.registerPanel()
}, [shares, open, isMobile])
const half = shares ? (chat.open ? "top" : false) : stacked
useEffect(() => {
if (!open) return
@@ -284,12 +302,19 @@ export function SidePanel({
data-testid={testId}
data-expanded={expanded || undefined}
className={cn(
"pointer-events-auto absolute z-20 flex flex-col overflow-hidden rounded-lg border border-border bg-card/80 shadow-e2 backdrop-blur-md",
"pointer-events-auto z-20 flex flex-col overflow-hidden rounded-lg border border-border bg-card/80 shadow-e2 backdrop-blur-md",
fixed ? "fixed" : "absolute",
expanded
? // Still a floating surface, given the whole inset: the toolbar
// and the flow-name box translate off screen while it is open.
"inset-4"
: "inset-y-4 right-4 w-[400px]",
: "right-4 w-[400px]",
!expanded &&
(half === "top"
? "top-4 bottom-[calc(50%+0.5rem)]"
: half === "bottom"
? "top-[calc(50%+0.5rem)] bottom-4"
: "inset-y-4"),
)}
>
{contents}