diff --git a/frontend/app/view/aifilediff/aifilediff.tsx b/frontend/app/view/aifilediff/aifilediff.tsx index 3b853a6eb6..f7ca817068 100644 --- a/frontend/app/view/aifilediff/aifilediff.tsx +++ b/frontend/app/view/aifilediff/aifilediff.tsx @@ -3,9 +3,9 @@ import type { BlockNodeModel } from "@/app/block/blocktypes"; import type { TabModel } from "@/app/store/tab-model"; -import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; import { DiffViewer } from "@/app/view/codeeditor/diffviewer"; +import type { WaveEnv, WaveEnvSubset } from "@/app/waveenv/waveenv"; import { globalStore, WOS } from "@/store/global"; import { base64ToString } from "@/util/util"; import * as jotai from "jotai"; @@ -17,10 +17,18 @@ type DiffData = { fileName: string; }; +export type AiFileDiffEnv = WaveEnvSubset<{ + rpc: { + WaveAIGetToolDiffCommand: WaveEnv["rpc"]["WaveAIGetToolDiffCommand"]; + }; + wos: WaveEnv["wos"]; +}>; + export class AiFileDiffViewModel implements ViewModel { blockId: string; nodeModel: BlockNodeModel; tabModel: TabModel; + env: AiFileDiffEnv; viewType = "aifilediff"; blockAtom: jotai.Atom; diffDataAtom: jotai.PrimitiveAtom; @@ -30,11 +38,12 @@ export class AiFileDiffViewModel implements ViewModel { viewName: jotai.Atom; viewText: jotai.Atom; - constructor({ blockId, nodeModel, tabModel }: ViewModelInitType) { + constructor({ blockId, nodeModel, tabModel, waveEnv }: ViewModelInitType) { this.blockId = blockId; this.nodeModel = nodeModel; this.tabModel = tabModel; - this.blockAtom = WOS.getWaveObjectAtom(`block:${blockId}`); + this.env = waveEnv as AiFileDiffEnv; + this.blockAtom = this.env.wos.getWaveObjectAtom(`block:${blockId}`); this.diffDataAtom = jotai.atom(null) as jotai.PrimitiveAtom; this.errorAtom = jotai.atom(null) as jotai.PrimitiveAtom; this.loadingAtom = jotai.atom(true); @@ -76,7 +85,7 @@ function AiFileDiffView({ blockId, model }: ViewComponentProps { + it("encodes the default diff content for the mock rpc response", () => { + const response = makeMockAiFileDiffResponse(); + + expect(base64ToString(response.originalcontents64)).toBe(DefaultAiFileDiffOriginal); + expect(base64ToString(response.modifiedcontents64)).toBe(DefaultAiFileDiffModified); + }); + + it("accepts custom original and modified content", () => { + const response = makeMockAiFileDiffResponse("before", "after"); + + expect(base64ToString(response.originalcontents64)).toBe("before"); + expect(base64ToString(response.modifiedcontents64)).toBe("after"); + }); +}); diff --git a/frontend/preview/previews/aifilediff.preview.tsx b/frontend/preview/previews/aifilediff.preview.tsx new file mode 100644 index 0000000000..3cd0125765 --- /dev/null +++ b/frontend/preview/previews/aifilediff.preview.tsx @@ -0,0 +1,145 @@ +// Copyright 2026, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { Block } from "@/app/block/block"; +import { globalStore } from "@/app/store/jotaiStore"; +import { getTabModelByTabId, TabModelContext } from "@/app/store/tab-model"; +import { useWaveEnv, WaveEnvContext } from "@/app/waveenv/waveenv"; +import type { NodeModel } from "@/layout/index"; +import { atom } from "jotai"; +import * as React from "react"; +import { applyMockEnvOverrides, MockWaveEnv } from "../mock/mockwaveenv"; +import { + DefaultAiFileDiffChatId, + DefaultAiFileDiffFileName, + DefaultAiFileDiffToolCallId, + makeMockAiFileDiffResponse, +} from "./aifilediff.preview-util"; + +const PreviewWorkspaceId = "preview-aifilediff-workspace"; +const PreviewTabId = "preview-aifilediff-tab"; +const PreviewNodeId = "preview-aifilediff-node"; +const PreviewBlockId = "preview-aifilediff-block"; + +function makeMockWorkspace(): Workspace { + return { + otype: "workspace", + oid: PreviewWorkspaceId, + version: 1, + name: "Preview Workspace", + tabids: [PreviewTabId], + activetabid: PreviewTabId, + meta: {}, + } as Workspace; +} + +function makeMockTab(): Tab { + return { + otype: "tab", + oid: PreviewTabId, + version: 1, + name: "AI File Diff Preview", + blockids: [PreviewBlockId], + meta: {}, + } as Tab; +} + +function makeMockBlock(): Block { + return { + otype: "block", + oid: PreviewBlockId, + version: 1, + meta: { + view: "aifilediff", + file: DefaultAiFileDiffFileName, + "aifilediff:chatid": DefaultAiFileDiffChatId, + "aifilediff:toolcallid": DefaultAiFileDiffToolCallId, + }, + } as Block; +} + +function makePreviewNodeModel(): NodeModel { + const isFocusedAtom = atom(true); + const isMagnifiedAtom = atom(false); + + return { + additionalProps: atom({} as any), + innerRect: atom({ width: "1000px", height: "640px" }), + blockNum: atom(1), + numLeafs: atom(1), + nodeId: PreviewNodeId, + blockId: PreviewBlockId, + addEphemeralNodeToLayout: () => {}, + animationTimeS: atom(0), + isResizing: atom(false), + isFocused: isFocusedAtom, + isMagnified: isMagnifiedAtom, + anyMagnified: atom(false), + isEphemeral: atom(false), + ready: atom(true), + disablePointerEvents: atom(false), + toggleMagnify: () => { + globalStore.set(isMagnifiedAtom, !globalStore.get(isMagnifiedAtom)); + }, + focusNode: () => { + globalStore.set(isFocusedAtom, true); + }, + onClose: () => {}, + dragHandleRef: { current: null }, + displayContainerRef: { current: null }, + }; +} + +function AiFileDiffPreviewInner() { + const baseEnv = useWaveEnv(); + const nodeModel = React.useMemo(() => makePreviewNodeModel(), []); + + const env = React.useMemo(() => { + const mockWaveObjs: Record = { + [`workspace:${PreviewWorkspaceId}`]: makeMockWorkspace(), + [`tab:${PreviewTabId}`]: makeMockTab(), + [`block:${PreviewBlockId}`]: makeMockBlock(), + }; + + return applyMockEnvOverrides(baseEnv, { + tabId: PreviewTabId, + mockWaveObjs, + atoms: { + workspaceId: atom(PreviewWorkspaceId), + staticTabId: atom(PreviewTabId), + }, + rpc: { + WaveAIGetToolDiffCommand: async (_client, data) => { + if ( + data.chatid !== DefaultAiFileDiffChatId || + data.toolcallid !== DefaultAiFileDiffToolCallId + ) { + return null; + } + return makeMockAiFileDiffResponse(); + }, + }, + }); + }, [baseEnv]); + + const tabModel = React.useMemo(() => getTabModelByTabId(PreviewTabId, env), [env]); + + return ( + + +
+
full aifilediff block (mock WOS + mock WaveAI diff RPC)
+
+
+ +
+
+
+
+
+ ); +} + +export function AiFileDiffPreview() { + return ; +}