diff --git a/.nx/version-plans/flow-copy-paste-undo.md b/.nx/version-plans/flow-copy-paste-undo.md new file mode 100644 index 000000000..1b0afa39c --- /dev/null +++ b/.nx/version-plans/flow-copy-paste-undo.md @@ -0,0 +1,5 @@ +--- +desktop: minor +--- + +Add flow node copy/paste and canvas undo/redo support. diff --git a/packages/client/package.json b/packages/client/package.json index a298c82e7..6a5a6992b 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -33,7 +33,6 @@ "@hookform/resolvers": "catalog:", "@lezer/highlight": "catalog:", "@lezer/lr": "catalog:", - "@openreplay/tracker": "catalog:", "@prettier/plugin-xml": "catalog:", "@react-aria/collections": "catalog:", "@standard-schema/spec": "catalog:", diff --git a/packages/client/src/app/index.tsx b/packages/client/src/app/index.tsx index c1bb1c681..1ed04a8b9 100644 --- a/packages/client/src/app/index.tsx +++ b/packages/client/src/app/index.tsx @@ -11,7 +11,6 @@ import { makeToastQueue } from '@the-dev-tools/ui/toast'; import { ApiCollections, ApiTransport } from '~/shared/api'; import { runtimeAtom } from '~/shared/lib/runtime'; import { RouterContext } from './context'; -import { startOpenReplay } from './open-replay'; import { router } from './router'; import { initUmami } from './umami'; @@ -23,7 +22,6 @@ const appAtom = runtimeAtom.atom( // Telemetry startup should never block app rendering. void Runtime.runPromise(runtime)(initUmami).catch(() => undefined); - void Runtime.runPromise(runtime)(startOpenReplay).catch(() => undefined); yield* ApiCollections; const transport = yield* ApiTransport; diff --git a/packages/client/src/app/open-replay.tsx b/packages/client/src/app/open-replay.tsx deleted file mode 100644 index 8305e8c20..000000000 --- a/packages/client/src/app/open-replay.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import OpenReplayTracker from '@openreplay/tracker'; -import { Config, Data, Effect, Option, pipe, Redacted } from 'effect'; - -export class StartOpenReplayError extends Data.TaggedError('StartOpenReplayError')<{ reason: string }> {} - -export const startOpenReplay = Effect.gen(function* () { - const configNamespace = Config.nested('PUBLIC_OPEN_REPLAY'); - - const track = yield* pipe( - Config.boolean('TRACK'), - configNamespace, - Config.orElse(() => Config.succeed(false)), - ); - if (!track) return; - - const projectKey = yield* pipe(Config.redacted('PROJECT_KEY'), configNamespace); - const tracker = new OpenReplayTracker({ - projectKey: Redacted.value(projectKey), - - __DISABLE_SECURE_MODE: true, - - network: { - captureInIframes: true, - capturePayload: true, - failuresOnly: false, - ignoreHeaders: false, - sessionTokenHeader: false, - }, - }); - - const sessionName = yield* pipe(Config.string('SESSION_NAME'), configNamespace, Config.option); - Option.map(sessionName, (_) => void tracker.setMetadata('session-name', _)); - - const userId = yield* pipe(Config.string('USER_ID'), configNamespace, Config.option); - Option.map(userId, (_) => void tracker.setUserID(_)); - - const result = yield* Effect.promise(() => tracker.start()); - if (!result.success) return yield* Effect.fail(new StartOpenReplayError({ reason: result.reason })); - yield* Effect.logInfo('Tracking started', { ...result, sessionName, userId }); - - yield* Effect.addFinalizer(() => Effect.sync(() => tracker.stop())); -}); diff --git a/packages/client/src/pages/flow/agent-panel.tsx b/packages/client/src/pages/flow/agent-panel.tsx index 118484350..71a08e8c3 100644 --- a/packages/client/src/pages/flow/agent-panel.tsx +++ b/packages/client/src/pages/flow/agent-panel.tsx @@ -1,5 +1,4 @@ import { eq, useLiveQuery } from '@tanstack/react-db'; -import * as XF from '@xyflow/react'; import { Ulid } from 'id128'; import { FormEvent, KeyboardEvent, use, useEffect, useMemo, useRef, useState } from 'react'; import { FiArrowUp, FiChevronUp, FiEdit, FiSettings, FiX } from 'react-icons/fi'; @@ -12,7 +11,7 @@ import { type Message, type ToolCall, useAgentChat } from '~/features/agent'; import { type AgentProvider, useAgentProviderKey } from '~/features/agent/use-agent-provider-key'; import { useApiCollection } from '~/shared/api'; import { FlowContext } from './context'; -import { nodeClientCollection } from './node'; +import { useFlowSelection } from './selection'; // --------------------------------------------------------------------------- // Tool call display helpers @@ -87,10 +86,7 @@ const PROVIDER_OPTIONS: Record< export const AgentPanel = () => { const { flowId, setAgentPanelOpen } = use(FlowContext); const { apiKey, provider, setApiKey, setProvider } = useAgentProviderKey(); - const selectedNodeIds = XF.useStore( - (s) => s.nodes.filter((n) => n.selected).map((n) => n.id), - (a, b) => a.length === b.length && a.every((id, i) => id === b[i]), - ); + const { deselectAll, deselectNodes, selectedNodeIds } = useFlowSelection(); const { cancel, clearMessages, error, isLoading, messages, sendMessage, streamingContent } = useAgentChat({ apiKey, flowId, @@ -231,7 +227,13 @@ export const AgentPanel = () => { className={tw`m-2 mt-0 rounded-[4px] border border-(--border-1) bg-(--surface-4) px-2.5 py-1.5`} data-agent-composer > - {selectedNodeIds.length > 0 && } + {selectedNodeIds.length > 0 && ( + + )}