+ );
+};
diff --git a/telemetry/ui/src/components/routes/builder/CodePreview.tsx b/telemetry/ui/src/components/routes/builder/CodePreview.tsx
new file mode 100644
index 000000000..ae759b418
--- /dev/null
+++ b/telemetry/ui/src/components/routes/builder/CodePreview.tsx
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
+import { base16AteliersulphurpoolLight } from 'react-syntax-highlighter/dist/esm/styles/prism';
+
+export const CodePreview = (props: { code: string }) => {
+ const copyToClipboard = () => {
+ navigator.clipboard.writeText(props.code);
+ };
+
+ return (
+
+
+ Generated Python
+
+
+
+
+ {props.code}
+
+
+
+ );
+};
diff --git a/telemetry/ui/src/components/routes/builder/NodeEditor.tsx b/telemetry/ui/src/components/routes/builder/NodeEditor.tsx
new file mode 100644
index 000000000..c572f2761
--- /dev/null
+++ b/telemetry/ui/src/components/routes/builder/NodeEditor.tsx
@@ -0,0 +1,359 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { useState } from 'react';
+import { BuilderNode, NodeType, NODE_TYPE_META } from '../../../utils/codeGenerator';
+import { TrashIcon } from '@heroicons/react/24/outline';
+
+const TagInput = (props: {
+ label: string;
+ values: string[];
+ onChange: (values: string[]) => void;
+ colorClass: string;
+}) => {
+ const [input, setInput] = useState('');
+
+ const addTag = () => {
+ const trimmed = input.trim();
+ if (trimmed && !props.values.includes(trimmed)) {
+ props.onChange([...props.values, trimmed]);
+ setInput('');
+ }
+ };
+
+ return (
+